diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63761697b8833e8391084201a66b8c8fb1ed9de7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/override.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/override.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..778e7029ceba092b3b0958efdb48deea9cd05b46 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/_distutils_hack/__pycache__/override.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/einops-0.8.1.dist-info/licenses/LICENSE b/URSA/.venv_ursa/lib/python3.12/site-packages/einops-0.8.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..3a654e906619009358eb2cfe80609bd12b43fa7f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/einops-0.8.1.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Alex Rogozhnikov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab7076b34db8e7e6b27b1cb7a212dcdc50264576 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..176a7f8fabfce8466d8a5bd7a301cf6755068fde Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29d3a9cd8858ae0ed6db255ab711a63dba7b2723 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_windows.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_windows.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..010004be4ac313e1c3b3b23018f66fa5c764c18a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/_windows.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/asyncio.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/asyncio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..320296faaafe90aab46a49cd9a4ba8185fc72a4a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/filelock/__pycache__/asyncio.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06d285aa20aa792bb16cf0cf03b1875bff37bd00 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c30224bd07cb56ffc19538d759a0b28bcfc828c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab3c439fea20c2ad2692916d8d8af6774541dbe2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..defca6381508138aefb87f43a5755321efa97112 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0a8a246125f1061d0d31d30ee9864180554b351 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d69fb2d3839291818134773caa270894f9d6c963 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22c91451b8c7baa4344a2f0e19dfea0a7ba14e9d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c475d1097fc5b4b0351cef02fcd82214fa8f96b8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc4b8f7cdf3774eb39a3b5d823cb1d66438c39ac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b921907fbaa88b1a6fc452f2c339992e0bed3ef Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc1e51742ce296cd51b50e981ec842c9041665b9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f85a51cb5e6648aedf790dea70d2eedd05d5b51f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/helpers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8659aa0a38969fa788d3e8a5e9288f3b7e0e7c7a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/helpers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_against_stdlib_http.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_against_stdlib_http.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec2131787bc0b0f0e611c78d71793fbd28b6a082 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_against_stdlib_http.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_connection.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_connection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87ffc1daf3d9ff1aa1ca2756327725bf955299c4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_connection.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_events.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_events.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..04b5a0dfbbb35161156e3cf347e93d672121faa6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_events.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_headers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_headers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c1ec48899fd622b517e96f9b88d956f8b0e3a2e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_headers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_helpers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8af48ca82b559651ce996a34a3edf383ef4ddb28 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_helpers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_io.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_io.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e272f5fdf10502c707e9d94f11ce56a78e4f00bd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_io.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_receivebuffer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_receivebuffer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84356a0c8cd1bd68f2fbfa6298a1cab17bd2c332 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_receivebuffer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_state.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_state.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3efb14f1f8bfd2f02489f86932fc9009a0259eec Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_state.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_util.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f712b7258e8c1cbd2bcdd54935b1d55e4104258 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/__pycache__/test_util.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/data/test-file b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/data/test-file new file mode 100644 index 0000000000000000000000000000000000000000..d0be0a6c5d6c19a35f0e815b83e27f8d833ea88b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/data/test-file @@ -0,0 +1 @@ +92b12bc045050b55b848d37167a1a63947c364579889ce1d39788e45e9fac9e5 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/helpers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..571be44461b0847c9edb8654c9d528abed0b7800 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/helpers.py @@ -0,0 +1,101 @@ +from typing import cast, List, Type, Union, ValuesView + +from .._connection import Connection, NEED_DATA, PAUSED +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .._state import CLIENT, CLOSED, DONE, MUST_CLOSE, SERVER +from .._util import Sentinel + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + + +def get_all_events(conn: Connection) -> List[Event]: + got_events = [] + while True: + event = conn.next_event() + if event in (NEED_DATA, PAUSED): + break + event = cast(Event, event) + got_events.append(event) + if type(event) is ConnectionClosed: + break + return got_events + + +def receive_and_get(conn: Connection, data: bytes) -> List[Event]: + conn.receive_data(data) + return get_all_events(conn) + + +# Merges adjacent Data events, converts payloads to bytestrings, and removes +# chunk boundaries. +def normalize_data_events(in_events: List[Event]) -> List[Event]: + out_events: List[Event] = [] + for event in in_events: + if type(event) is Data: + event = Data(data=bytes(event.data), chunk_start=False, chunk_end=False) + if out_events and type(out_events[-1]) is type(event) is Data: + out_events[-1] = Data( + data=out_events[-1].data + event.data, + chunk_start=out_events[-1].chunk_start, + chunk_end=out_events[-1].chunk_end, + ) + else: + out_events.append(event) + return out_events + + +# Given that we want to write tests that push some events through a Connection +# and check that its state updates appropriately... we might as make a habit +# of pushing them through two Connections with a fake network link in +# between. +class ConnectionPair: + def __init__(self) -> None: + self.conn = {CLIENT: Connection(CLIENT), SERVER: Connection(SERVER)} + self.other = {CLIENT: SERVER, SERVER: CLIENT} + + @property + def conns(self) -> ValuesView[Connection]: + return self.conn.values() + + # expect="match" if expect=send_events; expect=[...] to say what expected + def send( + self, + role: Type[Sentinel], + send_events: Union[List[Event], Event], + expect: Union[List[Event], Event, Literal["match"]] = "match", + ) -> bytes: + if not isinstance(send_events, list): + send_events = [send_events] + data = b"" + closed = False + for send_event in send_events: + new_data = self.conn[role].send(send_event) + if new_data is None: + closed = True + else: + data += new_data + # send uses b"" to mean b"", and None to mean closed + # receive uses b"" to mean closed, and None to mean "try again" + # so we have to translate between the two conventions + if data: + self.conn[self.other[role]].receive_data(data) + if closed: + self.conn[self.other[role]].receive_data(b"") + got_events = get_all_events(self.conn[self.other[role]]) + if expect == "match": + expect = send_events + if not isinstance(expect, list): + expect = [expect] + assert got_events == expect + return data diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_against_stdlib_http.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_against_stdlib_http.py new file mode 100644 index 0000000000000000000000000000000000000000..d2ee13149d34c9882432cdebfec87dff9814076d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_against_stdlib_http.py @@ -0,0 +1,115 @@ +import json +import os.path +import socket +import socketserver +import threading +from contextlib import closing, contextmanager +from http.server import SimpleHTTPRequestHandler +from typing import Callable, Generator +from urllib.request import urlopen + +import h11 + + +@contextmanager +def socket_server( + handler: Callable[..., socketserver.BaseRequestHandler] +) -> Generator[socketserver.TCPServer, None, None]: + httpd = socketserver.TCPServer(("127.0.0.1", 0), handler) + thread = threading.Thread( + target=httpd.serve_forever, kwargs={"poll_interval": 0.01} + ) + thread.daemon = True + try: + thread.start() + yield httpd + finally: + httpd.shutdown() + + +test_file_path = os.path.join(os.path.dirname(__file__), "data/test-file") +with open(test_file_path, "rb") as f: + test_file_data = f.read() + + +class SingleMindedRequestHandler(SimpleHTTPRequestHandler): + def translate_path(self, path: str) -> str: + return test_file_path + + +def test_h11_as_client() -> None: + with socket_server(SingleMindedRequestHandler) as httpd: + with closing(socket.create_connection(httpd.server_address)) as s: + c = h11.Connection(h11.CLIENT) + + s.sendall( + c.send( # type: ignore[arg-type] + h11.Request( + method="GET", target="/foo", headers=[("Host", "localhost")] + ) + ) + ) + s.sendall(c.send(h11.EndOfMessage())) # type: ignore[arg-type] + + data = bytearray() + while True: + event = c.next_event() + print(event) + if event is h11.NEED_DATA: + # Use a small read buffer to make things more challenging + # and exercise more paths :-) + c.receive_data(s.recv(10)) + continue + if type(event) is h11.Response: + assert event.status_code == 200 + if type(event) is h11.Data: + data += event.data + if type(event) is h11.EndOfMessage: + break + assert bytes(data) == test_file_data + + +class H11RequestHandler(socketserver.BaseRequestHandler): + def handle(self) -> None: + with closing(self.request) as s: + c = h11.Connection(h11.SERVER) + request = None + while True: + event = c.next_event() + if event is h11.NEED_DATA: + # Use a small read buffer to make things more challenging + # and exercise more paths :-) + c.receive_data(s.recv(10)) + continue + if type(event) is h11.Request: + request = event + if type(event) is h11.EndOfMessage: + break + assert request is not None + info = json.dumps( + { + "method": request.method.decode("ascii"), + "target": request.target.decode("ascii"), + "headers": { + name.decode("ascii"): value.decode("ascii") + for (name, value) in request.headers + }, + } + ) + s.sendall(c.send(h11.Response(status_code=200, headers=[]))) # type: ignore[arg-type] + s.sendall(c.send(h11.Data(data=info.encode("ascii")))) + s.sendall(c.send(h11.EndOfMessage())) + + +def test_h11_as_server() -> None: + with socket_server(H11RequestHandler) as httpd: + host, port = httpd.server_address + url = "http://{}:{}/some-path".format(host, port) + with closing(urlopen(url)) as f: + assert f.getcode() == 200 + data = f.read() + info = json.loads(data.decode("ascii")) + print(info) + assert info["method"] == "GET" + assert info["target"] == "/some-path" + assert "urllib" in info["headers"]["user-agent"] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_connection.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..73a27b98bebd949cb3b99e19a3a8a484455b58d7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_connection.py @@ -0,0 +1,1122 @@ +from typing import Any, cast, Dict, List, Optional, Tuple, Type + +import pytest + +from .._connection import _body_framing, _keep_alive, Connection, NEED_DATA, PAUSED +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .._state import ( + CLIENT, + CLOSED, + DONE, + ERROR, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from .._util import LocalProtocolError, RemoteProtocolError, Sentinel +from .helpers import ConnectionPair, get_all_events, receive_and_get + + +def test__keep_alive() -> None: + assert _keep_alive( + Request(method="GET", target="/", headers=[("Host", "Example.com")]) + ) + assert not _keep_alive( + Request( + method="GET", + target="/", + headers=[("Host", "Example.com"), ("Connection", "close")], + ) + ) + assert not _keep_alive( + Request( + method="GET", + target="/", + headers=[("Host", "Example.com"), ("Connection", "a, b, cLOse, foo")], + ) + ) + assert not _keep_alive( + Request(method="GET", target="/", headers=[], http_version="1.0") # type: ignore[arg-type] + ) + + assert _keep_alive(Response(status_code=200, headers=[])) # type: ignore[arg-type] + assert not _keep_alive(Response(status_code=200, headers=[("Connection", "close")])) + assert not _keep_alive( + Response(status_code=200, headers=[("Connection", "a, b, cLOse, foo")]) + ) + assert not _keep_alive(Response(status_code=200, headers=[], http_version="1.0")) # type: ignore[arg-type] + + +def test__body_framing() -> None: + def headers(cl: Optional[int], te: bool) -> List[Tuple[str, str]]: + headers = [] + if cl is not None: + headers.append(("Content-Length", str(cl))) + if te: + headers.append(("Transfer-Encoding", "chunked")) + return headers + + def resp( + status_code: int = 200, cl: Optional[int] = None, te: bool = False + ) -> Response: + return Response(status_code=status_code, headers=headers(cl, te)) + + def req(cl: Optional[int] = None, te: bool = False) -> Request: + h = headers(cl, te) + h += [("Host", "example.com")] + return Request(method="GET", target="/", headers=h) + + # Special cases where the headers are ignored: + for kwargs in [{}, {"cl": 100}, {"te": True}, {"cl": 100, "te": True}]: + kwargs = cast(Dict[str, Any], kwargs) + for meth, r in [ + (b"HEAD", resp(**kwargs)), + (b"GET", resp(status_code=204, **kwargs)), + (b"GET", resp(status_code=304, **kwargs)), + ]: + assert _body_framing(meth, r) == ("content-length", (0,)) + + # Transfer-encoding + for kwargs in [{"te": True}, {"cl": 100, "te": True}]: + kwargs = cast(Dict[str, Any], kwargs) + for meth, r in [(None, req(**kwargs)), (b"GET", resp(**kwargs))]: # type: ignore + assert _body_framing(meth, r) == ("chunked", ()) + + # Content-Length + for meth, r in [(None, req(cl=100)), (b"GET", resp(cl=100))]: # type: ignore + assert _body_framing(meth, r) == ("content-length", (100,)) + + # No headers + assert _body_framing(None, req()) == ("content-length", (0,)) # type: ignore + assert _body_framing(b"GET", resp()) == ("http/1.0", ()) + + +def test_Connection_basics_and_content_length() -> None: + with pytest.raises(ValueError): + Connection("CLIENT") # type: ignore + + p = ConnectionPair() + assert p.conn[CLIENT].our_role is CLIENT + assert p.conn[CLIENT].their_role is SERVER + assert p.conn[SERVER].our_role is SERVER + assert p.conn[SERVER].their_role is CLIENT + + data = p.send( + CLIENT, + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Content-Length", "10")], + ), + ) + assert data == ( + b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 10\r\n\r\n" + ) + + for conn in p.conns: + assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + assert p.conn[CLIENT].our_state is SEND_BODY + assert p.conn[CLIENT].their_state is SEND_RESPONSE + assert p.conn[SERVER].our_state is SEND_RESPONSE + assert p.conn[SERVER].their_state is SEND_BODY + + assert p.conn[CLIENT].their_http_version is None + assert p.conn[SERVER].their_http_version == b"1.1" + + data = p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type] + assert data == b"HTTP/1.1 100 \r\n\r\n" + + data = p.send(SERVER, Response(status_code=200, headers=[("Content-Length", "11")])) + assert data == b"HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\n" + + for conn in p.conns: + assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY} + + assert p.conn[CLIENT].their_http_version == b"1.1" + assert p.conn[SERVER].their_http_version == b"1.1" + + data = p.send(CLIENT, Data(data=b"12345")) + assert data == b"12345" + data = p.send( + CLIENT, Data(data=b"67890"), expect=[Data(data=b"67890"), EndOfMessage()] + ) + assert data == b"67890" + data = p.send(CLIENT, EndOfMessage(), expect=[]) + assert data == b"" + + for conn in p.conns: + assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY} + + data = p.send(SERVER, Data(data=b"1234567890")) + assert data == b"1234567890" + data = p.send(SERVER, Data(data=b"1"), expect=[Data(data=b"1"), EndOfMessage()]) + assert data == b"1" + data = p.send(SERVER, EndOfMessage(), expect=[]) + assert data == b"" + + for conn in p.conns: + assert conn.states == {CLIENT: DONE, SERVER: DONE} + + +def test_chunked() -> None: + p = ConnectionPair() + + p.send( + CLIENT, + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")], + ), + ) + data = p.send(CLIENT, Data(data=b"1234567890", chunk_start=True, chunk_end=True)) + assert data == b"a\r\n1234567890\r\n" + data = p.send(CLIENT, Data(data=b"abcde", chunk_start=True, chunk_end=True)) + assert data == b"5\r\nabcde\r\n" + data = p.send(CLIENT, Data(data=b""), expect=[]) + assert data == b"" + data = p.send(CLIENT, EndOfMessage(headers=[("hello", "there")])) + assert data == b"0\r\nhello: there\r\n\r\n" + + p.send( + SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")]) + ) + p.send(SERVER, Data(data=b"54321", chunk_start=True, chunk_end=True)) + p.send(SERVER, Data(data=b"12345", chunk_start=True, chunk_end=True)) + p.send(SERVER, EndOfMessage()) + + for conn in p.conns: + assert conn.states == {CLIENT: DONE, SERVER: DONE} + + +def test_chunk_boundaries() -> None: + conn = Connection(our_role=SERVER) + + request = ( + b"POST / HTTP/1.1\r\n" + b"Host: example.com\r\n" + b"Transfer-Encoding: chunked\r\n" + b"\r\n" + ) + conn.receive_data(request) + assert conn.next_event() == Request( + method="POST", + target="/", + headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")], + ) + assert conn.next_event() is NEED_DATA + + conn.receive_data(b"5\r\nhello\r\n") + assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True) + + conn.receive_data(b"5\r\nhel") + assert conn.next_event() == Data(data=b"hel", chunk_start=True, chunk_end=False) + + conn.receive_data(b"l") + assert conn.next_event() == Data(data=b"l", chunk_start=False, chunk_end=False) + + conn.receive_data(b"o\r\n") + assert conn.next_event() == Data(data=b"o", chunk_start=False, chunk_end=True) + + conn.receive_data(b"5\r\nhello") + assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True) + + conn.receive_data(b"\r\n") + assert conn.next_event() == NEED_DATA + + conn.receive_data(b"0\r\n\r\n") + assert conn.next_event() == EndOfMessage() + + +def test_client_talking_to_http10_server() -> None: + c = Connection(CLIENT) + c.send(Request(method="GET", target="/", headers=[("Host", "example.com")])) + c.send(EndOfMessage()) + assert c.our_state is DONE + # No content-length, so Http10 framing for body + assert receive_and_get(c, b"HTTP/1.0 200 OK\r\n\r\n") == [ + Response(status_code=200, headers=[], http_version="1.0", reason=b"OK") # type: ignore[arg-type] + ] + assert c.our_state is MUST_CLOSE + assert receive_and_get(c, b"12345") == [Data(data=b"12345")] + assert receive_and_get(c, b"67890") == [Data(data=b"67890")] + assert receive_and_get(c, b"") == [EndOfMessage(), ConnectionClosed()] + assert c.their_state is CLOSED + + +def test_server_talking_to_http10_client() -> None: + c = Connection(SERVER) + # No content-length, so no body + # NB: no host header + assert receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") == [ + Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type] + EndOfMessage(), + ] + assert c.their_state is MUST_CLOSE + + # We automatically Connection: close back at them + assert ( + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" + ) + + assert c.send(Data(data=b"12345")) == b"12345" + assert c.send(EndOfMessage()) == b"" + assert c.our_state is MUST_CLOSE + + # Check that it works if they do send Content-Length + c = Connection(SERVER) + # NB: no host header + assert receive_and_get(c, b"POST / HTTP/1.0\r\nContent-Length: 10\r\n\r\n1") == [ + Request( + method="POST", + target="/", + headers=[("Content-Length", "10")], + http_version="1.0", + ), + Data(data=b"1"), + ] + assert receive_and_get(c, b"234567890") == [Data(data=b"234567890"), EndOfMessage()] + assert c.their_state is MUST_CLOSE + assert receive_and_get(c, b"") == [ConnectionClosed()] + + +def test_automatic_transfer_encoding_in_response() -> None: + # Check that in responses, the user can specify either Transfer-Encoding: + # chunked or no framing at all, and in both cases we automatically select + # the right option depending on whether the peer speaks HTTP/1.0 or + # HTTP/1.1 + for user_headers in [ + [("Transfer-Encoding", "chunked")], + [], + # In fact, this even works if Content-Length is set, + # because if both are set then Transfer-Encoding wins + [("Transfer-Encoding", "chunked"), ("Content-Length", "100")], + ]: + user_headers = cast(List[Tuple[str, str]], user_headers) + p = ConnectionPair() + p.send( + CLIENT, + [ + Request(method="GET", target="/", headers=[("Host", "example.com")]), + EndOfMessage(), + ], + ) + # When speaking to HTTP/1.1 client, all of the above cases get + # normalized to Transfer-Encoding: chunked + p.send( + SERVER, + Response(status_code=200, headers=user_headers), + expect=Response( + status_code=200, headers=[("Transfer-Encoding", "chunked")] + ), + ) + + # When speaking to HTTP/1.0 client, all of the above cases get + # normalized to no-framing-headers + c = Connection(SERVER) + receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") + assert ( + c.send(Response(status_code=200, headers=user_headers)) + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" + ) + assert c.send(Data(data=b"12345")) == b"12345" + + +def test_automagic_connection_close_handling() -> None: + p = ConnectionPair() + # If the user explicitly sets Connection: close, then we notice and + # respect it + p.send( + CLIENT, + [ + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Connection", "close")], + ), + EndOfMessage(), + ], + ) + for conn in p.conns: + assert conn.states[CLIENT] is MUST_CLOSE + # And if the client sets it, the server automatically echoes it back + p.send( + SERVER, + # no header here... + [Response(status_code=204, headers=[]), EndOfMessage()], # type: ignore[arg-type] + # ...but oh look, it arrived anyway + expect=[ + Response(status_code=204, headers=[("connection", "close")]), + EndOfMessage(), + ], + ) + for conn in p.conns: + assert conn.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE} + + +def test_100_continue() -> None: + def setup() -> ConnectionPair: + p = ConnectionPair() + p.send( + CLIENT, + Request( + method="GET", + target="/", + headers=[ + ("Host", "example.com"), + ("Content-Length", "100"), + ("Expect", "100-continue"), + ], + ), + ) + for conn in p.conns: + assert conn.client_is_waiting_for_100_continue + assert not p.conn[CLIENT].they_are_waiting_for_100_continue + assert p.conn[SERVER].they_are_waiting_for_100_continue + return p + + # Disabled by 100 Continue + p = setup() + p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type] + for conn in p.conns: + assert not conn.client_is_waiting_for_100_continue + assert not conn.they_are_waiting_for_100_continue + + # Disabled by a real response + p = setup() + p.send( + SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")]) + ) + for conn in p.conns: + assert not conn.client_is_waiting_for_100_continue + assert not conn.they_are_waiting_for_100_continue + + # Disabled by the client going ahead and sending stuff anyway + p = setup() + p.send(CLIENT, Data(data=b"12345")) + for conn in p.conns: + assert not conn.client_is_waiting_for_100_continue + assert not conn.they_are_waiting_for_100_continue + + +def test_max_incomplete_event_size_countermeasure() -> None: + # Infinitely long headers are definitely not okay + c = Connection(SERVER) + c.receive_data(b"GET / HTTP/1.0\r\nEndless: ") + assert c.next_event() is NEED_DATA + with pytest.raises(RemoteProtocolError): + while True: + c.receive_data(b"a" * 1024) + c.next_event() + + # Checking that the same header is accepted / rejected depending on the + # max_incomplete_event_size setting: + c = Connection(SERVER, max_incomplete_event_size=5000) + c.receive_data(b"GET / HTTP/1.0\r\nBig: ") + c.receive_data(b"a" * 4000) + c.receive_data(b"\r\n\r\n") + assert get_all_events(c) == [ + Request( + method="GET", target="/", http_version="1.0", headers=[("big", "a" * 4000)] + ), + EndOfMessage(), + ] + + c = Connection(SERVER, max_incomplete_event_size=4000) + c.receive_data(b"GET / HTTP/1.0\r\nBig: ") + c.receive_data(b"a" * 4000) + with pytest.raises(RemoteProtocolError): + c.next_event() + + # Temporarily exceeding the size limit is fine, as long as its done with + # complete events: + c = Connection(SERVER, max_incomplete_event_size=5000) + c.receive_data(b"GET / HTTP/1.0\r\nContent-Length: 10000") + c.receive_data(b"\r\n\r\n" + b"a" * 10000) + assert get_all_events(c) == [ + Request( + method="GET", + target="/", + http_version="1.0", + headers=[("Content-Length", "10000")], + ), + Data(data=b"a" * 10000), + EndOfMessage(), + ] + + c = Connection(SERVER, max_incomplete_event_size=100) + # Two pipelined requests to create a way-too-big receive buffer... but + # it's fine because we're not checking + c.receive_data( + b"GET /1 HTTP/1.1\r\nHost: a\r\n\r\n" + b"GET /2 HTTP/1.1\r\nHost: b\r\n\r\n" + b"X" * 1000 + ) + assert get_all_events(c) == [ + Request(method="GET", target="/1", headers=[("host", "a")]), + EndOfMessage(), + ] + # Even more data comes in, still no problem + c.receive_data(b"X" * 1000) + # We can respond and reuse to get the second pipelined request + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + c.start_next_cycle() + assert get_all_events(c) == [ + Request(method="GET", target="/2", headers=[("host", "b")]), + EndOfMessage(), + ] + # But once we unpause and try to read the next message, and find that it's + # incomplete and the buffer is *still* way too large, then *that's* a + # problem: + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + c.start_next_cycle() + with pytest.raises(RemoteProtocolError): + c.next_event() + + +def test_reuse_simple() -> None: + p = ConnectionPair() + p.send( + CLIENT, + [Request(method="GET", target="/", headers=[("Host", "a")]), EndOfMessage()], + ) + p.send( + SERVER, + [ + Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]), + EndOfMessage(), + ], + ) + for conn in p.conns: + assert conn.states == {CLIENT: DONE, SERVER: DONE} + conn.start_next_cycle() + + p.send( + CLIENT, + [ + Request(method="DELETE", target="/foo", headers=[("Host", "a")]), + EndOfMessage(), + ], + ) + p.send( + SERVER, + [ + Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]), + EndOfMessage(), + ], + ) + + +def test_pipelining() -> None: + # Client doesn't support pipelining, so we have to do this by hand + c = Connection(SERVER) + assert c.next_event() is NEED_DATA + # 3 requests all bunched up + c.receive_data( + b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n" + b"12345" + b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n" + b"67890" + b"GET /3 HTTP/1.1\r\nHost: a.com\r\n\r\n" + ) + assert get_all_events(c) == [ + Request( + method="GET", + target="/1", + headers=[("Host", "a.com"), ("Content-Length", "5")], + ), + Data(data=b"12345"), + EndOfMessage(), + ] + assert c.their_state is DONE + assert c.our_state is SEND_RESPONSE + + assert c.next_event() is PAUSED + + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + assert c.their_state is DONE + assert c.our_state is DONE + + c.start_next_cycle() + + assert get_all_events(c) == [ + Request( + method="GET", + target="/2", + headers=[("Host", "a.com"), ("Content-Length", "5")], + ), + Data(data=b"67890"), + EndOfMessage(), + ] + assert c.next_event() is PAUSED + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + c.start_next_cycle() + + assert get_all_events(c) == [ + Request(method="GET", target="/3", headers=[("Host", "a.com")]), + EndOfMessage(), + ] + # Doesn't pause this time, no trailing data + assert c.next_event() is NEED_DATA + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + + # Arrival of more data triggers pause + assert c.next_event() is NEED_DATA + c.receive_data(b"SADF") + assert c.next_event() is PAUSED + assert c.trailing_data == (b"SADF", False) + # If EOF arrives while paused, we don't see that either: + c.receive_data(b"") + assert c.trailing_data == (b"SADF", True) + assert c.next_event() is PAUSED + c.receive_data(b"") + assert c.next_event() is PAUSED + # Can't call receive_data with non-empty buf after closing it + with pytest.raises(RuntimeError): + c.receive_data(b"FDSA") + + +def test_protocol_switch() -> None: + for (req, deny, accept) in [ + ( + Request( + method="CONNECT", + target="example.com:443", + headers=[("Host", "foo"), ("Content-Length", "1")], + ), + Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]), + Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]), + ), + ( + Request( + method="GET", + target="/", + headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")], + ), + Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]), + InformationalResponse(status_code=101, headers=[("Upgrade", "a")]), + ), + ( + Request( + method="CONNECT", + target="example.com:443", + headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")], + ), + Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]), + # Accept CONNECT, not upgrade + Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]), + ), + ( + Request( + method="CONNECT", + target="example.com:443", + headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")], + ), + Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]), + # Accept Upgrade, not CONNECT + InformationalResponse(status_code=101, headers=[("Upgrade", "b")]), + ), + ]: + + def setup() -> ConnectionPair: + p = ConnectionPair() + p.send(CLIENT, req) + # No switch-related state change stuff yet; the client has to + # finish the request before that kicks in + for conn in p.conns: + assert conn.states[CLIENT] is SEND_BODY + p.send(CLIENT, [Data(data=b"1"), EndOfMessage()]) + for conn in p.conns: + assert conn.states[CLIENT] is MIGHT_SWITCH_PROTOCOL + assert p.conn[SERVER].next_event() is PAUSED + return p + + # Test deny case + p = setup() + p.send(SERVER, deny) + for conn in p.conns: + assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY} + p.send(SERVER, EndOfMessage()) + # Check that re-use is still allowed after a denial + for conn in p.conns: + conn.start_next_cycle() + + # Test accept case + p = setup() + p.send(SERVER, accept) + for conn in p.conns: + assert conn.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL} + conn.receive_data(b"123") + assert conn.next_event() is PAUSED + conn.receive_data(b"456") + assert conn.next_event() is PAUSED + assert conn.trailing_data == (b"123456", False) + + # Pausing in might-switch, then recovery + # (weird artificial case where the trailing data actually is valid + # HTTP for some reason, because this makes it easier to test the state + # logic) + p = setup() + sc = p.conn[SERVER] + sc.receive_data(b"GET / HTTP/1.0\r\n\r\n") + assert sc.next_event() is PAUSED + assert sc.trailing_data == (b"GET / HTTP/1.0\r\n\r\n", False) + sc.send(deny) + assert sc.next_event() is PAUSED + sc.send(EndOfMessage()) + sc.start_next_cycle() + assert get_all_events(sc) == [ + Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type] + EndOfMessage(), + ] + + # When we're DONE, have no trailing data, and the connection gets + # closed, we report ConnectionClosed(). When we're in might-switch or + # switched, we don't. + p = setup() + sc = p.conn[SERVER] + sc.receive_data(b"") + assert sc.next_event() is PAUSED + assert sc.trailing_data == (b"", True) + p.send(SERVER, accept) + assert sc.next_event() is PAUSED + + p = setup() + sc = p.conn[SERVER] + sc.receive_data(b"") + assert sc.next_event() is PAUSED + sc.send(deny) + assert sc.next_event() == ConnectionClosed() + + # You can't send after switching protocols, or while waiting for a + # protocol switch + p = setup() + with pytest.raises(LocalProtocolError): + p.conn[CLIENT].send( + Request(method="GET", target="/", headers=[("Host", "a")]) + ) + p = setup() + p.send(SERVER, accept) + with pytest.raises(LocalProtocolError): + p.conn[SERVER].send(Data(data=b"123")) + + +def test_close_simple() -> None: + # Just immediately closing a new connection without anything having + # happened yet. + for (who_shot_first, who_shot_second) in [(CLIENT, SERVER), (SERVER, CLIENT)]: + + def setup() -> ConnectionPair: + p = ConnectionPair() + p.send(who_shot_first, ConnectionClosed()) + for conn in p.conns: + assert conn.states == { + who_shot_first: CLOSED, + who_shot_second: MUST_CLOSE, + } + return p + + # You can keep putting b"" into a closed connection, and you keep + # getting ConnectionClosed() out: + p = setup() + assert p.conn[who_shot_second].next_event() == ConnectionClosed() + assert p.conn[who_shot_second].next_event() == ConnectionClosed() + p.conn[who_shot_second].receive_data(b"") + assert p.conn[who_shot_second].next_event() == ConnectionClosed() + # Second party can close... + p = setup() + p.send(who_shot_second, ConnectionClosed()) + for conn in p.conns: + assert conn.our_state is CLOSED + assert conn.their_state is CLOSED + # But trying to receive new data on a closed connection is a + # RuntimeError (not ProtocolError, because the problem here isn't + # violation of HTTP, it's violation of physics) + p = setup() + with pytest.raises(RuntimeError): + p.conn[who_shot_second].receive_data(b"123") + # And receiving new data on a MUST_CLOSE connection is a ProtocolError + p = setup() + p.conn[who_shot_first].receive_data(b"GET") + with pytest.raises(RemoteProtocolError): + p.conn[who_shot_first].next_event() + + +def test_close_different_states() -> None: + req = [ + Request(method="GET", target="/foo", headers=[("Host", "a")]), + EndOfMessage(), + ] + resp = [ + Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]), + EndOfMessage(), + ] + + # Client before request + p = ConnectionPair() + p.send(CLIENT, ConnectionClosed()) + for conn in p.conns: + assert conn.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE} + + # Client after request + p = ConnectionPair() + p.send(CLIENT, req) + p.send(CLIENT, ConnectionClosed()) + for conn in p.conns: + assert conn.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE} + + # Server after request -> not allowed + p = ConnectionPair() + p.send(CLIENT, req) + with pytest.raises(LocalProtocolError): + p.conn[SERVER].send(ConnectionClosed()) + p.conn[CLIENT].receive_data(b"") + with pytest.raises(RemoteProtocolError): + p.conn[CLIENT].next_event() + + # Server after response + p = ConnectionPair() + p.send(CLIENT, req) + p.send(SERVER, resp) + p.send(SERVER, ConnectionClosed()) + for conn in p.conns: + assert conn.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED} + + # Both after closing (ConnectionClosed() is idempotent) + p = ConnectionPair() + p.send(CLIENT, req) + p.send(SERVER, resp) + p.send(CLIENT, ConnectionClosed()) + p.send(SERVER, ConnectionClosed()) + p.send(CLIENT, ConnectionClosed()) + p.send(SERVER, ConnectionClosed()) + + # In the middle of sending -> not allowed + p = ConnectionPair() + p.send( + CLIENT, + Request( + method="GET", target="/", headers=[("Host", "a"), ("Content-Length", "10")] + ), + ) + with pytest.raises(LocalProtocolError): + p.conn[CLIENT].send(ConnectionClosed()) + p.conn[SERVER].receive_data(b"") + with pytest.raises(RemoteProtocolError): + p.conn[SERVER].next_event() + + +# Receive several requests and then client shuts down their side of the +# connection; we can respond to each +def test_pipelined_close() -> None: + c = Connection(SERVER) + # 2 requests then a close + c.receive_data( + b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n" + b"12345" + b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n" + b"67890" + ) + c.receive_data(b"") + assert get_all_events(c) == [ + Request( + method="GET", + target="/1", + headers=[("host", "a.com"), ("content-length", "5")], + ), + Data(data=b"12345"), + EndOfMessage(), + ] + assert c.states[CLIENT] is DONE + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + assert c.states[SERVER] is DONE + c.start_next_cycle() + assert get_all_events(c) == [ + Request( + method="GET", + target="/2", + headers=[("host", "a.com"), ("content-length", "5")], + ), + Data(data=b"67890"), + EndOfMessage(), + ConnectionClosed(), + ] + assert c.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE} + c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type] + c.send(EndOfMessage()) + assert c.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE} + c.send(ConnectionClosed()) + assert c.states == {CLIENT: CLOSED, SERVER: CLOSED} + + +def test_sendfile() -> None: + class SendfilePlaceholder: + def __len__(self) -> int: + return 10 + + placeholder = SendfilePlaceholder() + + def setup( + header: Tuple[str, str], http_version: str + ) -> Tuple[Connection, Optional[List[bytes]]]: + c = Connection(SERVER) + receive_and_get( + c, "GET / HTTP/{}\r\nHost: a\r\n\r\n".format(http_version).encode("ascii") + ) + headers = [] + if header: + headers.append(header) + c.send(Response(status_code=200, headers=headers)) + return c, c.send_with_data_passthrough(Data(data=placeholder)) # type: ignore + + c, data = setup(("Content-Length", "10"), "1.1") + assert data == [placeholder] # type: ignore + # Raises an error if the connection object doesn't think we've sent + # exactly 10 bytes + c.send(EndOfMessage()) + + _, data = setup(("Transfer-Encoding", "chunked"), "1.1") + assert placeholder in data # type: ignore + data[data.index(placeholder)] = b"x" * 10 # type: ignore + assert b"".join(data) == b"a\r\nxxxxxxxxxx\r\n" # type: ignore + + c, data = setup(None, "1.0") # type: ignore + assert data == [placeholder] # type: ignore + assert c.our_state is SEND_BODY + + +def test_errors() -> None: + # After a receive error, you can't receive + for role in [CLIENT, SERVER]: + c = Connection(our_role=role) + c.receive_data(b"gibberish\r\n\r\n") + with pytest.raises(RemoteProtocolError): + c.next_event() + # Now any attempt to receive continues to raise + assert c.their_state is ERROR + assert c.our_state is not ERROR + print(c._cstate.states) + with pytest.raises(RemoteProtocolError): + c.next_event() + # But we can still yell at the client for sending us gibberish + if role is SERVER: + assert ( + c.send(Response(status_code=400, headers=[])) # type: ignore[arg-type] + == b"HTTP/1.1 400 \r\nConnection: close\r\n\r\n" + ) + + # After an error sending, you can no longer send + # (This is especially important for things like content-length errors, + # where there's complex internal state being modified) + def conn(role: Type[Sentinel]) -> Connection: + c = Connection(our_role=role) + if role is SERVER: + # Put it into the state where it *could* send a response... + receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") + assert c.our_state is SEND_RESPONSE + return c + + for role in [CLIENT, SERVER]: + if role is CLIENT: + # This HTTP/1.0 request won't be detected as bad until after we go + # through the state machine and hit the writing code + good = Request(method="GET", target="/", headers=[("Host", "example.com")]) + bad = Request( + method="GET", + target="/", + headers=[("Host", "example.com")], + http_version="1.0", + ) + elif role is SERVER: + good = Response(status_code=200, headers=[]) # type: ignore[arg-type,assignment] + bad = Response(status_code=200, headers=[], http_version="1.0") # type: ignore[arg-type,assignment] + # Make sure 'good' actually is good + c = conn(role) + c.send(good) + assert c.our_state is not ERROR + # Do that again, but this time sending 'bad' first + c = conn(role) + with pytest.raises(LocalProtocolError): + c.send(bad) + assert c.our_state is ERROR + assert c.their_state is not ERROR + # Now 'good' is not so good + with pytest.raises(LocalProtocolError): + c.send(good) + + # And check send_failed() too + c = conn(role) + c.send_failed() + assert c.our_state is ERROR + assert c.their_state is not ERROR + # This is idempotent + c.send_failed() + assert c.our_state is ERROR + assert c.their_state is not ERROR + + +def test_idle_receive_nothing() -> None: + # At one point this incorrectly raised an error + for role in [CLIENT, SERVER]: + c = Connection(role) + assert c.next_event() is NEED_DATA + + +def test_connection_drop() -> None: + c = Connection(SERVER) + c.receive_data(b"GET /") + assert c.next_event() is NEED_DATA + c.receive_data(b"") + with pytest.raises(RemoteProtocolError): + c.next_event() + + +def test_408_request_timeout() -> None: + # Should be able to send this spontaneously as a server without seeing + # anything from client + p = ConnectionPair() + p.send(SERVER, Response(status_code=408, headers=[(b"connection", b"close")])) + + +# This used to raise IndexError +def test_empty_request() -> None: + c = Connection(SERVER) + c.receive_data(b"\r\n") + with pytest.raises(RemoteProtocolError): + c.next_event() + + +# This used to raise IndexError +def test_empty_response() -> None: + c = Connection(CLIENT) + c.send(Request(method="GET", target="/", headers=[("Host", "a")])) + c.receive_data(b"\r\n") + with pytest.raises(RemoteProtocolError): + c.next_event() + + +@pytest.mark.parametrize( + "data", + [ + b"\x00", + b"\x20", + b"\x16\x03\x01\x00\xa5", # Typical start of a TLS Client Hello + ], +) +def test_early_detection_of_invalid_request(data: bytes) -> None: + c = Connection(SERVER) + # Early detection should occur before even receiving a `\r\n` + c.receive_data(data) + with pytest.raises(RemoteProtocolError): + c.next_event() + + +@pytest.mark.parametrize( + "data", + [ + b"\x00", + b"\x20", + b"\x16\x03\x03\x00\x31", # Typical start of a TLS Server Hello + ], +) +def test_early_detection_of_invalid_response(data: bytes) -> None: + c = Connection(CLIENT) + # Early detection should occur before even receiving a `\r\n` + c.receive_data(data) + with pytest.raises(RemoteProtocolError): + c.next_event() + + +# This used to give different headers for HEAD and GET. +# The correct way to handle HEAD is to put whatever headers we *would* have +# put if it were a GET -- even though we know that for HEAD, those headers +# will be ignored. +def test_HEAD_framing_headers() -> None: + def setup(method: bytes, http_version: bytes) -> Connection: + c = Connection(SERVER) + c.receive_data( + method + b" / HTTP/" + http_version + b"\r\n" + b"Host: example.com\r\n\r\n" + ) + assert type(c.next_event()) is Request + assert type(c.next_event()) is EndOfMessage + return c + + for method in [b"GET", b"HEAD"]: + # No Content-Length, HTTP/1.1 peer, should use chunked + c = setup(method, b"1.1") + assert ( + c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type] + b"Transfer-Encoding: chunked\r\n\r\n" + ) + + # No Content-Length, HTTP/1.0 peer, frame with connection: close + c = setup(method, b"1.0") + assert ( + c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type] + b"Connection: close\r\n\r\n" + ) + + # Content-Length + Transfer-Encoding, TE wins + c = setup(method, b"1.1") + assert ( + c.send( + Response( + status_code=200, + headers=[ + ("Content-Length", "100"), + ("Transfer-Encoding", "chunked"), + ], + ) + ) + == b"HTTP/1.1 200 \r\n" + b"Transfer-Encoding: chunked\r\n\r\n" + ) + + +def test_special_exceptions_for_lost_connection_in_message_body() -> None: + c = Connection(SERVER) + c.receive_data( + b"POST / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 100\r\n\r\n" + ) + assert type(c.next_event()) is Request + assert c.next_event() is NEED_DATA + c.receive_data(b"12345") + assert c.next_event() == Data(data=b"12345") + c.receive_data(b"") + with pytest.raises(RemoteProtocolError) as excinfo: + c.next_event() + assert "received 5 bytes" in str(excinfo.value) + assert "expected 100" in str(excinfo.value) + + c = Connection(SERVER) + c.receive_data( + b"POST / HTTP/1.1\r\n" + b"Host: example.com\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" + ) + assert type(c.next_event()) is Request + assert c.next_event() is NEED_DATA + c.receive_data(b"8\r\n012345") + assert c.next_event().data == b"012345" # type: ignore + c.receive_data(b"") + with pytest.raises(RemoteProtocolError) as excinfo: + c.next_event() + assert "incomplete chunked read" in str(excinfo.value) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_events.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_events.py new file mode 100644 index 0000000000000000000000000000000000000000..bc6c3137063888e05ec4af77bed6c6fd1a3d1594 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_events.py @@ -0,0 +1,150 @@ +from http import HTTPStatus + +import pytest + +from .. import _events +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .._util import LocalProtocolError + + +def test_events() -> None: + with pytest.raises(LocalProtocolError): + # Missing Host: + req = Request( + method="GET", target="/", headers=[("a", "b")], http_version="1.1" + ) + # But this is okay (HTTP/1.0) + req = Request(method="GET", target="/", headers=[("a", "b")], http_version="1.0") + # fields are normalized + assert req.method == b"GET" + assert req.target == b"/" + assert req.headers == [(b"a", b"b")] + assert req.http_version == b"1.0" + + # This is also okay -- has a Host (with weird capitalization, which is ok) + req = Request( + method="GET", + target="/", + headers=[("a", "b"), ("hOSt", "example.com")], + http_version="1.1", + ) + # we normalize header capitalization + assert req.headers == [(b"a", b"b"), (b"host", b"example.com")] + + # Multiple host is bad too + with pytest.raises(LocalProtocolError): + req = Request( + method="GET", + target="/", + headers=[("Host", "a"), ("Host", "a")], + http_version="1.1", + ) + # Even for HTTP/1.0 + with pytest.raises(LocalProtocolError): + req = Request( + method="GET", + target="/", + headers=[("Host", "a"), ("Host", "a")], + http_version="1.0", + ) + + # Header values are validated + for bad_char in "\x00\r\n\f\v": + with pytest.raises(LocalProtocolError): + req = Request( + method="GET", + target="/", + headers=[("Host", "a"), ("Foo", "asd" + bad_char)], + http_version="1.0", + ) + + # But for compatibility we allow non-whitespace control characters, even + # though they're forbidden by the spec. + Request( + method="GET", + target="/", + headers=[("Host", "a"), ("Foo", "asd\x01\x02\x7f")], + http_version="1.0", + ) + + # Request target is validated + for bad_byte in b"\x00\x20\x7f\xee": + target = bytearray(b"/") + target.append(bad_byte) + with pytest.raises(LocalProtocolError): + Request( + method="GET", target=target, headers=[("Host", "a")], http_version="1.1" + ) + + # Request method is validated + with pytest.raises(LocalProtocolError): + Request( + method="GET / HTTP/1.1", + target=target, + headers=[("Host", "a")], + http_version="1.1", + ) + + ir = InformationalResponse(status_code=100, headers=[("Host", "a")]) + assert ir.status_code == 100 + assert ir.headers == [(b"host", b"a")] + assert ir.http_version == b"1.1" + + with pytest.raises(LocalProtocolError): + InformationalResponse(status_code=200, headers=[("Host", "a")]) + + resp = Response(status_code=204, headers=[], http_version="1.0") # type: ignore[arg-type] + assert resp.status_code == 204 + assert resp.headers == [] + assert resp.http_version == b"1.0" + + with pytest.raises(LocalProtocolError): + resp = Response(status_code=100, headers=[], http_version="1.0") # type: ignore[arg-type] + + with pytest.raises(LocalProtocolError): + Response(status_code="100", headers=[], http_version="1.0") # type: ignore[arg-type] + + with pytest.raises(LocalProtocolError): + InformationalResponse(status_code=b"100", headers=[], http_version="1.0") # type: ignore[arg-type] + + d = Data(data=b"asdf") + assert d.data == b"asdf" + + eom = EndOfMessage() + assert eom.headers == [] + + cc = ConnectionClosed() + assert repr(cc) == "ConnectionClosed()" + + +def test_intenum_status_code() -> None: + # https://github.com/python-hyper/h11/issues/72 + + r = Response(status_code=HTTPStatus.OK, headers=[], http_version="1.0") # type: ignore[arg-type] + assert r.status_code == HTTPStatus.OK + assert type(r.status_code) is not type(HTTPStatus.OK) + assert type(r.status_code) is int + + +def test_header_casing() -> None: + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert len(r.headers) == 2 + assert r.headers[0] == (b"host", b"example.org") + assert r.headers == [(b"host", b"example.org"), (b"connection", b"keep-alive")] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive"), + ] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_headers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_headers.py new file mode 100644 index 0000000000000000000000000000000000000000..ba53d088f6f8d22dede47873a03b70a7103da7ea --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_headers.py @@ -0,0 +1,157 @@ +import pytest + +from .._events import Request +from .._headers import ( + get_comma_header, + has_expect_100_continue, + Headers, + normalize_and_validate, + set_comma_header, +) +from .._util import LocalProtocolError + + +def test_normalize_and_validate() -> None: + assert normalize_and_validate([("foo", "bar")]) == [(b"foo", b"bar")] + assert normalize_and_validate([(b"foo", b"bar")]) == [(b"foo", b"bar")] + + # no leading/trailing whitespace in names + with pytest.raises(LocalProtocolError): + normalize_and_validate([(b"foo ", "bar")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([(b" foo", "bar")]) + + # no weird characters in names + with pytest.raises(LocalProtocolError) as excinfo: + normalize_and_validate([(b"foo bar", b"baz")]) + assert "foo bar" in str(excinfo.value) + with pytest.raises(LocalProtocolError): + normalize_and_validate([(b"foo\x00bar", b"baz")]) + # Not even 8-bit characters: + with pytest.raises(LocalProtocolError): + normalize_and_validate([(b"foo\xffbar", b"baz")]) + # And not even the control characters we allow in values: + with pytest.raises(LocalProtocolError): + normalize_and_validate([(b"foo\x01bar", b"baz")]) + + # no return or NUL characters in values + with pytest.raises(LocalProtocolError) as excinfo: + normalize_and_validate([("foo", "bar\rbaz")]) + assert "bar\\rbaz" in str(excinfo.value) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", "bar\nbaz")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", "bar\x00baz")]) + # no leading/trailing whitespace + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", "barbaz ")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", " barbaz")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", "barbaz\t")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("foo", "\tbarbaz")]) + + # content-length + assert normalize_and_validate([("Content-Length", "1")]) == [ + (b"content-length", b"1") + ] + with pytest.raises(LocalProtocolError): + normalize_and_validate([("Content-Length", "asdf")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("Content-Length", "1x")]) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("Content-Length", "1"), ("Content-Length", "2")]) + assert normalize_and_validate( + [("Content-Length", "0"), ("Content-Length", "0")] + ) == [(b"content-length", b"0")] + assert normalize_and_validate([("Content-Length", "0 , 0")]) == [ + (b"content-length", b"0") + ] + with pytest.raises(LocalProtocolError): + normalize_and_validate( + [("Content-Length", "1"), ("Content-Length", "1"), ("Content-Length", "2")] + ) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("Content-Length", "1 , 1,2")]) + + # transfer-encoding + assert normalize_and_validate([("Transfer-Encoding", "chunked")]) == [ + (b"transfer-encoding", b"chunked") + ] + assert normalize_and_validate([("Transfer-Encoding", "cHuNkEd")]) == [ + (b"transfer-encoding", b"chunked") + ] + with pytest.raises(LocalProtocolError) as excinfo: + normalize_and_validate([("Transfer-Encoding", "gzip")]) + assert excinfo.value.error_status_hint == 501 # Not Implemented + with pytest.raises(LocalProtocolError) as excinfo: + normalize_and_validate( + [("Transfer-Encoding", "chunked"), ("Transfer-Encoding", "gzip")] + ) + assert excinfo.value.error_status_hint == 501 # Not Implemented + + +def test_get_set_comma_header() -> None: + headers = normalize_and_validate( + [ + ("Connection", "close"), + ("whatever", "something"), + ("connectiON", "fOo,, , BAR"), + ] + ) + + assert get_comma_header(headers, b"connection") == [b"close", b"foo", b"bar"] + + headers = set_comma_header(headers, b"newthing", ["a", "b"]) # type: ignore + + with pytest.raises(LocalProtocolError): + set_comma_header(headers, b"newthing", [" a", "b"]) # type: ignore + + assert headers == [ + (b"connection", b"close"), + (b"whatever", b"something"), + (b"connection", b"fOo,, , BAR"), + (b"newthing", b"a"), + (b"newthing", b"b"), + ] + + headers = set_comma_header(headers, b"whatever", ["different thing"]) # type: ignore + + assert headers == [ + (b"connection", b"close"), + (b"connection", b"fOo,, , BAR"), + (b"newthing", b"a"), + (b"newthing", b"b"), + (b"whatever", b"different thing"), + ] + + +def test_has_100_continue() -> None: + assert has_expect_100_continue( + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Expect", "100-continue")], + ) + ) + assert not has_expect_100_continue( + Request(method="GET", target="/", headers=[("Host", "example.com")]) + ) + # Case insensitive + assert has_expect_100_continue( + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Expect", "100-Continue")], + ) + ) + # Doesn't work in HTTP/1.0 + assert not has_expect_100_continue( + Request( + method="GET", + target="/", + headers=[("Host", "example.com"), ("Expect", "100-continue")], + http_version="1.0", + ) + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_helpers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..c329c767834f73b1dd8991a4ac12d4972a41e98a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_helpers.py @@ -0,0 +1,32 @@ +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .helpers import normalize_data_events + + +def test_normalize_data_events() -> None: + assert normalize_data_events( + [ + Data(data=bytearray(b"1")), + Data(data=b"2"), + Response(status_code=200, headers=[]), # type: ignore[arg-type] + Data(data=b"3"), + Data(data=b"4"), + EndOfMessage(), + Data(data=b"5"), + Data(data=b"6"), + Data(data=b"7"), + ] + ) == [ + Data(data=b"12"), + Response(status_code=200, headers=[]), # type: ignore[arg-type] + Data(data=b"34"), + EndOfMessage(), + Data(data=b"567"), + ] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_io.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_io.py new file mode 100644 index 0000000000000000000000000000000000000000..2b47c0eac92162646c8b60bd3de65bff1951c895 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_io.py @@ -0,0 +1,572 @@ +from typing import Any, Callable, Generator, List + +import pytest + +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .._headers import Headers, normalize_and_validate +from .._readers import ( + _obsolete_line_fold, + ChunkedReader, + ContentLengthReader, + Http10Reader, + READERS, +) +from .._receivebuffer import ReceiveBuffer +from .._state import ( + CLIENT, + CLOSED, + DONE, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from .._util import LocalProtocolError +from .._writers import ( + ChunkedWriter, + ContentLengthWriter, + Http10Writer, + write_any_response, + write_headers, + write_request, + WRITERS, +) +from .helpers import normalize_data_events + +SIMPLE_CASES = [ + ( + (CLIENT, IDLE), + Request( + method="GET", + target="/a", + headers=[("Host", "foo"), ("Connection", "close")], + ), + b"GET /a HTTP/1.1\r\nHost: foo\r\nConnection: close\r\n\r\n", + ), + ( + (SERVER, SEND_RESPONSE), + Response(status_code=200, headers=[("Connection", "close")], reason=b"OK"), + b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", + ), + ( + (SERVER, SEND_RESPONSE), + Response(status_code=200, headers=[], reason=b"OK"), # type: ignore[arg-type] + b"HTTP/1.1 200 OK\r\n\r\n", + ), + ( + (SERVER, SEND_RESPONSE), + InformationalResponse( + status_code=101, headers=[("Upgrade", "websocket")], reason=b"Upgrade" + ), + b"HTTP/1.1 101 Upgrade\r\nUpgrade: websocket\r\n\r\n", + ), + ( + (SERVER, SEND_RESPONSE), + InformationalResponse(status_code=101, headers=[], reason=b"Upgrade"), # type: ignore[arg-type] + b"HTTP/1.1 101 Upgrade\r\n\r\n", + ), +] + + +def dowrite(writer: Callable[..., None], obj: Any) -> bytes: + got_list: List[bytes] = [] + writer(obj, got_list.append) + return b"".join(got_list) + + +def tw(writer: Any, obj: Any, expected: Any) -> None: + got = dowrite(writer, obj) + assert got == expected + + +def makebuf(data: bytes) -> ReceiveBuffer: + buf = ReceiveBuffer() + buf += data + return buf + + +def tr(reader: Any, data: bytes, expected: Any) -> None: + def check(got: Any) -> None: + assert got == expected + # Headers should always be returned as bytes, not e.g. bytearray + # https://github.com/python-hyper/wsproto/pull/54#issuecomment-377709478 + for name, value in getattr(got, "headers", []): + assert type(name) is bytes + assert type(value) is bytes + + # Simple: consume whole thing + buf = makebuf(data) + check(reader(buf)) + assert not buf + + # Incrementally growing buffer + buf = ReceiveBuffer() + for i in range(len(data)): + assert reader(buf) is None + buf += data[i : i + 1] + check(reader(buf)) + + # Trailing data + buf = makebuf(data) + buf += b"trailing" + check(reader(buf)) + assert bytes(buf) == b"trailing" + + +def test_writers_simple() -> None: + for ((role, state), event, binary) in SIMPLE_CASES: + tw(WRITERS[role, state], event, binary) + + +def test_readers_simple() -> None: + for ((role, state), event, binary) in SIMPLE_CASES: + tr(READERS[role, state], binary, event) + + +def test_writers_unusual() -> None: + # Simple test of the write_headers utility routine + tw( + write_headers, + normalize_and_validate([("foo", "bar"), ("baz", "quux")]), + b"foo: bar\r\nbaz: quux\r\n\r\n", + ) + tw(write_headers, Headers([]), b"\r\n") + + # We understand HTTP/1.0, but we don't speak it + with pytest.raises(LocalProtocolError): + tw( + write_request, + Request( + method="GET", + target="/", + headers=[("Host", "foo"), ("Connection", "close")], + http_version="1.0", + ), + None, + ) + with pytest.raises(LocalProtocolError): + tw( + write_any_response, + Response( + status_code=200, headers=[("Connection", "close")], http_version="1.0" + ), + None, + ) + + +def test_readers_unusual() -> None: + # Reading HTTP/1.0 + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.0\r\nSome: header\r\n\r\n", + Request( + method="HEAD", + target="/foo", + headers=[("Some", "header")], + http_version="1.0", + ), + ) + + # check no-headers, since it's only legal with HTTP/1.0 + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.0\r\n\r\n", + Request(method="HEAD", target="/foo", headers=[], http_version="1.0"), # type: ignore[arg-type] + ) + + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200 OK\r\nSome: header\r\n\r\n", + Response( + status_code=200, + headers=[("Some", "header")], + http_version="1.0", + reason=b"OK", + ), + ) + + # single-character header values (actually disallowed by the ABNF in RFC + # 7230 -- this is a bug in the standard that we originally copied...) + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200 OK\r\n" b"Foo: a a a a a \r\n\r\n", + Response( + status_code=200, + headers=[("Foo", "a a a a a")], + http_version="1.0", + reason=b"OK", + ), + ) + + # Empty headers -- also legal + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200 OK\r\n" b"Foo:\r\n\r\n", + Response( + status_code=200, headers=[("Foo", "")], http_version="1.0", reason=b"OK" + ), + ) + + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200 OK\r\n" b"Foo: \t \t \r\n\r\n", + Response( + status_code=200, headers=[("Foo", "")], http_version="1.0", reason=b"OK" + ), + ) + + # Tolerate broken servers that leave off the response code + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.0 200\r\n" b"Foo: bar\r\n\r\n", + Response( + status_code=200, headers=[("Foo", "bar")], http_version="1.0", reason=b"" + ), + ) + + # Tolerate headers line endings (\r\n and \n) + # \n\r\b between headers and body + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.1 200 OK\r\nSomeHeader: val\n\r\n", + Response( + status_code=200, + headers=[("SomeHeader", "val")], + http_version="1.1", + reason="OK", + ), + ) + + # delimited only with \n + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.1 200 OK\nSomeHeader1: val1\nSomeHeader2: val2\n\n", + Response( + status_code=200, + headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")], + http_version="1.1", + reason="OK", + ), + ) + + # mixed \r\n and \n + tr( + READERS[SERVER, SEND_RESPONSE], + b"HTTP/1.1 200 OK\r\nSomeHeader1: val1\nSomeHeader2: val2\n\r\n", + Response( + status_code=200, + headers=[("SomeHeader1", "val1"), ("SomeHeader2", "val2")], + http_version="1.1", + reason="OK", + ), + ) + + # obsolete line folding + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" + b"Host: example.com\r\n" + b"Some: multi-line\r\n" + b" header\r\n" + b"\tnonsense\r\n" + b" \t \t\tI guess\r\n" + b"Connection: close\r\n" + b"More-nonsense: in the\r\n" + b" last header \r\n\r\n", + Request( + method="HEAD", + target="/foo", + headers=[ + ("Host", "example.com"), + ("Some", "multi-line header nonsense I guess"), + ("Connection", "close"), + ("More-nonsense", "in the last header"), + ], + ), + ) + + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" b" folded: line\r\n\r\n", + None, + ) + + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" b"foo : line\r\n\r\n", + None, + ) + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" b"foo\t: line\r\n\r\n", + None, + ) + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" b"foo\t: line\r\n\r\n", + None, + ) + with pytest.raises(LocalProtocolError): + tr(READERS[CLIENT, IDLE], b"HEAD /foo HTTP/1.1\r\n" b": line\r\n\r\n", None) + + +def test__obsolete_line_fold_bytes() -> None: + # _obsolete_line_fold has a defensive cast to bytearray, which is + # necessary to protect against O(n^2) behavior in case anyone ever passes + # in regular bytestrings... but right now we never pass in regular + # bytestrings. so this test just exists to get some coverage on that + # defensive cast. + assert list(_obsolete_line_fold([b"aaa", b"bbb", b" ccc", b"ddd"])) == [ + b"aaa", + bytearray(b"bbb ccc"), + b"ddd", + ] + + +def _run_reader_iter( + reader: Any, buf: bytes, do_eof: bool +) -> Generator[Any, None, None]: + while True: + event = reader(buf) + if event is None: + break + yield event + # body readers have undefined behavior after returning EndOfMessage, + # because this changes the state so they don't get called again + if type(event) is EndOfMessage: + break + if do_eof: + assert not buf + yield reader.read_eof() + + +def _run_reader(*args: Any) -> List[Event]: + events = list(_run_reader_iter(*args)) + return normalize_data_events(events) + + +def t_body_reader(thunk: Any, data: bytes, expected: Any, do_eof: bool = False) -> None: + # Simple: consume whole thing + print("Test 1") + buf = makebuf(data) + assert _run_reader(thunk(), buf, do_eof) == expected + + # Incrementally growing buffer + print("Test 2") + reader = thunk() + buf = ReceiveBuffer() + events = [] + for i in range(len(data)): + events += _run_reader(reader, buf, False) + buf += data[i : i + 1] + events += _run_reader(reader, buf, do_eof) + assert normalize_data_events(events) == expected + + is_complete = any(type(event) is EndOfMessage for event in expected) + if is_complete and not do_eof: + buf = makebuf(data + b"trailing") + assert _run_reader(thunk(), buf, False) == expected + + +def test_ContentLengthReader() -> None: + t_body_reader(lambda: ContentLengthReader(0), b"", [EndOfMessage()]) + + t_body_reader( + lambda: ContentLengthReader(10), + b"0123456789", + [Data(data=b"0123456789"), EndOfMessage()], + ) + + +def test_Http10Reader() -> None: + t_body_reader(Http10Reader, b"", [EndOfMessage()], do_eof=True) + t_body_reader(Http10Reader, b"asdf", [Data(data=b"asdf")], do_eof=False) + t_body_reader( + Http10Reader, b"asdf", [Data(data=b"asdf"), EndOfMessage()], do_eof=True + ) + + +def test_ChunkedReader() -> None: + t_body_reader(ChunkedReader, b"0\r\n\r\n", [EndOfMessage()]) + + t_body_reader( + ChunkedReader, + b"0\r\nSome: header\r\n\r\n", + [EndOfMessage(headers=[("Some", "header")])], + ) + + t_body_reader( + ChunkedReader, + b"5\r\n01234\r\n" + + b"10\r\n0123456789abcdef\r\n" + + b"0\r\n" + + b"Some: header\r\n\r\n", + [ + Data(data=b"012340123456789abcdef"), + EndOfMessage(headers=[("Some", "header")]), + ], + ) + + t_body_reader( + ChunkedReader, + b"5\r\n01234\r\n" + b"10\r\n0123456789abcdef\r\n" + b"0\r\n\r\n", + [Data(data=b"012340123456789abcdef"), EndOfMessage()], + ) + + # handles upper and lowercase hex + t_body_reader( + ChunkedReader, + b"aA\r\n" + b"x" * 0xAA + b"\r\n" + b"0\r\n\r\n", + [Data(data=b"x" * 0xAA), EndOfMessage()], + ) + + # refuses arbitrarily long chunk integers + with pytest.raises(LocalProtocolError): + # Technically this is legal HTTP/1.1, but we refuse to process chunk + # sizes that don't fit into 20 characters of hex + t_body_reader(ChunkedReader, b"9" * 100 + b"\r\nxxx", [Data(data=b"xxx")]) + + # refuses garbage in the chunk count + with pytest.raises(LocalProtocolError): + t_body_reader(ChunkedReader, b"10\x00\r\nxxx", None) + + # handles (and discards) "chunk extensions" omg wtf + t_body_reader( + ChunkedReader, + b"5; hello=there\r\n" + + b"xxxxx" + + b"\r\n" + + b'0; random="junk"; some=more; canbe=lonnnnngg\r\n\r\n', + [Data(data=b"xxxxx"), EndOfMessage()], + ) + + t_body_reader( + ChunkedReader, + b"5 \r\n01234\r\n" + b"0\r\n\r\n", + [Data(data=b"01234"), EndOfMessage()], + ) + + +def test_ContentLengthWriter() -> None: + w = ContentLengthWriter(5) + assert dowrite(w, Data(data=b"123")) == b"123" + assert dowrite(w, Data(data=b"45")) == b"45" + assert dowrite(w, EndOfMessage()) == b"" + + w = ContentLengthWriter(5) + with pytest.raises(LocalProtocolError): + dowrite(w, Data(data=b"123456")) + + w = ContentLengthWriter(5) + dowrite(w, Data(data=b"123")) + with pytest.raises(LocalProtocolError): + dowrite(w, Data(data=b"456")) + + w = ContentLengthWriter(5) + dowrite(w, Data(data=b"123")) + with pytest.raises(LocalProtocolError): + dowrite(w, EndOfMessage()) + + w = ContentLengthWriter(5) + dowrite(w, Data(data=b"123")) == b"123" + dowrite(w, Data(data=b"45")) == b"45" + with pytest.raises(LocalProtocolError): + dowrite(w, EndOfMessage(headers=[("Etag", "asdf")])) + + +def test_ChunkedWriter() -> None: + w = ChunkedWriter() + assert dowrite(w, Data(data=b"aaa")) == b"3\r\naaa\r\n" + assert dowrite(w, Data(data=b"a" * 20)) == b"14\r\n" + b"a" * 20 + b"\r\n" + + assert dowrite(w, Data(data=b"")) == b"" + + assert dowrite(w, EndOfMessage()) == b"0\r\n\r\n" + + assert ( + dowrite(w, EndOfMessage(headers=[("Etag", "asdf"), ("a", "b")])) + == b"0\r\nEtag: asdf\r\na: b\r\n\r\n" + ) + + +def test_Http10Writer() -> None: + w = Http10Writer() + assert dowrite(w, Data(data=b"1234")) == b"1234" + assert dowrite(w, EndOfMessage()) == b"" + + with pytest.raises(LocalProtocolError): + dowrite(w, EndOfMessage(headers=[("Etag", "asdf")])) + + +def test_reject_garbage_after_request_line() -> None: + with pytest.raises(LocalProtocolError): + tr(READERS[SERVER, SEND_RESPONSE], b"HTTP/1.0 200 OK\x00xxxx\r\n\r\n", None) + + +def test_reject_garbage_after_response_line() -> None: + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1 xxxxxx\r\n" b"Host: a\r\n\r\n", + None, + ) + + +def test_reject_garbage_in_header_line() -> None: + with pytest.raises(LocalProtocolError): + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" b"Host: foo\x00bar\r\n\r\n", + None, + ) + + +def test_reject_non_vchar_in_path() -> None: + for bad_char in b"\x00\x20\x7f\xee": + message = bytearray(b"HEAD /") + message.append(bad_char) + message.extend(b" HTTP/1.1\r\nHost: foobar\r\n\r\n") + with pytest.raises(LocalProtocolError): + tr(READERS[CLIENT, IDLE], message, None) + + +# https://github.com/python-hyper/h11/issues/57 +def test_allow_some_garbage_in_cookies() -> None: + tr( + READERS[CLIENT, IDLE], + b"HEAD /foo HTTP/1.1\r\n" + b"Host: foo\r\n" + b"Set-Cookie: ___utmvafIumyLc=kUd\x01UpAt; path=/; Max-Age=900\r\n" + b"\r\n", + Request( + method="HEAD", + target="/foo", + headers=[ + ("Host", "foo"), + ("Set-Cookie", "___utmvafIumyLc=kUd\x01UpAt; path=/; Max-Age=900"), + ], + ), + ) + + +def test_host_comes_first() -> None: + tw( + write_headers, + normalize_and_validate([("foo", "bar"), ("Host", "example.com")]), + b"Host: example.com\r\nfoo: bar\r\n\r\n", + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_receivebuffer.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_receivebuffer.py new file mode 100644 index 0000000000000000000000000000000000000000..21a3870b6be9a6a365037326c2085ff2715022cc --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_receivebuffer.py @@ -0,0 +1,135 @@ +import re +from typing import Tuple + +import pytest + +from .._receivebuffer import ReceiveBuffer + + +def test_receivebuffer() -> None: + b = ReceiveBuffer() + assert not b + assert len(b) == 0 + assert bytes(b) == b"" + + b += b"123" + assert b + assert len(b) == 3 + assert bytes(b) == b"123" + + assert bytes(b) == b"123" + + assert b.maybe_extract_at_most(2) == b"12" + assert b + assert len(b) == 1 + assert bytes(b) == b"3" + + assert bytes(b) == b"3" + + assert b.maybe_extract_at_most(10) == b"3" + assert bytes(b) == b"" + + assert b.maybe_extract_at_most(10) is None + assert not b + + ################################################################ + # maybe_extract_until_next + ################################################################ + + b += b"123\n456\r\n789\r\n" + + assert b.maybe_extract_next_line() == b"123\n456\r\n" + assert bytes(b) == b"789\r\n" + + assert b.maybe_extract_next_line() == b"789\r\n" + assert bytes(b) == b"" + + b += b"12\r" + assert b.maybe_extract_next_line() is None + assert bytes(b) == b"12\r" + + b += b"345\n\r" + assert b.maybe_extract_next_line() is None + assert bytes(b) == b"12\r345\n\r" + + # here we stopped at the middle of b"\r\n" delimiter + + b += b"\n6789aaa123\r\n" + assert b.maybe_extract_next_line() == b"12\r345\n\r\n" + assert b.maybe_extract_next_line() == b"6789aaa123\r\n" + assert b.maybe_extract_next_line() is None + assert bytes(b) == b"" + + ################################################################ + # maybe_extract_lines + ################################################################ + + b += b"123\r\na: b\r\nfoo:bar\r\n\r\ntrailing" + lines = b.maybe_extract_lines() + assert lines == [b"123", b"a: b", b"foo:bar"] + assert bytes(b) == b"trailing" + + assert b.maybe_extract_lines() is None + + b += b"\r\n\r" + assert b.maybe_extract_lines() is None + + assert b.maybe_extract_at_most(100) == b"trailing\r\n\r" + assert not b + + # Empty body case (as happens at the end of chunked encoding if there are + # no trailing headers, e.g.) + b += b"\r\ntrailing" + assert b.maybe_extract_lines() == [] + assert bytes(b) == b"trailing" + + +@pytest.mark.parametrize( + "data", + [ + pytest.param( + ( + b"HTTP/1.1 200 OK\r\n", + b"Content-type: text/plain\r\n", + b"Connection: close\r\n", + b"\r\n", + b"Some body", + ), + id="with_crlf_delimiter", + ), + pytest.param( + ( + b"HTTP/1.1 200 OK\n", + b"Content-type: text/plain\n", + b"Connection: close\n", + b"\n", + b"Some body", + ), + id="with_lf_only_delimiter", + ), + pytest.param( + ( + b"HTTP/1.1 200 OK\n", + b"Content-type: text/plain\r\n", + b"Connection: close\n", + b"\n", + b"Some body", + ), + id="with_mixed_crlf_and_lf", + ), + ], +) +def test_receivebuffer_for_invalid_delimiter(data: Tuple[bytes]) -> None: + b = ReceiveBuffer() + + for line in data: + b += line + + lines = b.maybe_extract_lines() + + assert lines == [ + b"HTTP/1.1 200 OK", + b"Content-type: text/plain", + b"Connection: close", + ] + assert bytes(b) == b"Some body" diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_state.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_state.py new file mode 100644 index 0000000000000000000000000000000000000000..bc974e636e9f3e9b66022d2095cd670a9acbdcd9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_state.py @@ -0,0 +1,271 @@ +import pytest + +from .._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from .._state import ( + _SWITCH_CONNECT, + _SWITCH_UPGRADE, + CLIENT, + CLOSED, + ConnectionState, + DONE, + IDLE, + MIGHT_SWITCH_PROTOCOL, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, + SWITCHED_PROTOCOL, +) +from .._util import LocalProtocolError + + +def test_ConnectionState() -> None: + cs = ConnectionState() + + # Basic event-triggered transitions + + assert cs.states == {CLIENT: IDLE, SERVER: IDLE} + + cs.process_event(CLIENT, Request) + # The SERVER-Request special case: + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + # Illegal transitions raise an error and nothing happens + with pytest.raises(LocalProtocolError): + cs.process_event(CLIENT, Request) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + cs.process_event(SERVER, InformationalResponse) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + cs.process_event(SERVER, Response) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY} + + cs.process_event(CLIENT, EndOfMessage) + cs.process_event(SERVER, EndOfMessage) + assert cs.states == {CLIENT: DONE, SERVER: DONE} + + # State-triggered transition + + cs.process_event(SERVER, ConnectionClosed) + assert cs.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED} + + +def test_ConnectionState_keep_alive() -> None: + # keep_alive = False + cs = ConnectionState() + cs.process_event(CLIENT, Request) + cs.process_keep_alive_disabled() + cs.process_event(CLIENT, EndOfMessage) + assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_RESPONSE} + + cs.process_event(SERVER, Response) + cs.process_event(SERVER, EndOfMessage) + assert cs.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE} + + +def test_ConnectionState_keep_alive_in_DONE() -> None: + # Check that if keep_alive is disabled when the CLIENT is already in DONE, + # then this is sufficient to immediately trigger the DONE -> MUST_CLOSE + # transition + cs = ConnectionState() + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + assert cs.states[CLIENT] is DONE + cs.process_keep_alive_disabled() + assert cs.states[CLIENT] is MUST_CLOSE + + +def test_ConnectionState_switch_denied() -> None: + for switch_type in (_SWITCH_CONNECT, _SWITCH_UPGRADE): + for deny_early in (True, False): + cs = ConnectionState() + cs.process_client_switch_proposal(switch_type) + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, Data) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + assert switch_type in cs.pending_switch_proposals + + if deny_early: + # before client reaches DONE + cs.process_event(SERVER, Response) + assert not cs.pending_switch_proposals + + cs.process_event(CLIENT, EndOfMessage) + + if deny_early: + assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY} + else: + assert cs.states == { + CLIENT: MIGHT_SWITCH_PROTOCOL, + SERVER: SEND_RESPONSE, + } + + cs.process_event(SERVER, InformationalResponse) + assert cs.states == { + CLIENT: MIGHT_SWITCH_PROTOCOL, + SERVER: SEND_RESPONSE, + } + + cs.process_event(SERVER, Response) + assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY} + assert not cs.pending_switch_proposals + + +_response_type_for_switch = { + _SWITCH_UPGRADE: InformationalResponse, + _SWITCH_CONNECT: Response, + None: Response, +} + + +def test_ConnectionState_protocol_switch_accepted() -> None: + for switch_event in [_SWITCH_UPGRADE, _SWITCH_CONNECT]: + cs = ConnectionState() + cs.process_client_switch_proposal(switch_event) + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, Data) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + cs.process_event(CLIENT, EndOfMessage) + assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE} + + cs.process_event(SERVER, InformationalResponse) + assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE} + + cs.process_event(SERVER, _response_type_for_switch[switch_event], switch_event) + assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL} + + +def test_ConnectionState_double_protocol_switch() -> None: + # CONNECT + Upgrade is legal! Very silly, but legal. So we support + # it. Because sometimes doing the silly thing is easier than not. + for server_switch in [None, _SWITCH_UPGRADE, _SWITCH_CONNECT]: + cs = ConnectionState() + cs.process_client_switch_proposal(_SWITCH_UPGRADE) + cs.process_client_switch_proposal(_SWITCH_CONNECT) + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE} + cs.process_event( + SERVER, _response_type_for_switch[server_switch], server_switch + ) + if server_switch is None: + assert cs.states == {CLIENT: DONE, SERVER: SEND_BODY} + else: + assert cs.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL} + + +def test_ConnectionState_inconsistent_protocol_switch() -> None: + for client_switches, server_switch in [ + ([], _SWITCH_CONNECT), + ([], _SWITCH_UPGRADE), + ([_SWITCH_UPGRADE], _SWITCH_CONNECT), + ([_SWITCH_CONNECT], _SWITCH_UPGRADE), + ]: + cs = ConnectionState() + for client_switch in client_switches: # type: ignore[attr-defined] + cs.process_client_switch_proposal(client_switch) + cs.process_event(CLIENT, Request) + with pytest.raises(LocalProtocolError): + cs.process_event(SERVER, Response, server_switch) + + +def test_ConnectionState_keepalive_protocol_switch_interaction() -> None: + # keep_alive=False + pending_switch_proposals + cs = ConnectionState() + cs.process_client_switch_proposal(_SWITCH_UPGRADE) + cs.process_event(CLIENT, Request) + cs.process_keep_alive_disabled() + cs.process_event(CLIENT, Data) + assert cs.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE} + + # the protocol switch "wins" + cs.process_event(CLIENT, EndOfMessage) + assert cs.states == {CLIENT: MIGHT_SWITCH_PROTOCOL, SERVER: SEND_RESPONSE} + + # but when the server denies the request, keep_alive comes back into play + cs.process_event(SERVER, Response) + assert cs.states == {CLIENT: MUST_CLOSE, SERVER: SEND_BODY} + + +def test_ConnectionState_reuse() -> None: + cs = ConnectionState() + + with pytest.raises(LocalProtocolError): + cs.start_next_cycle() + + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + + with pytest.raises(LocalProtocolError): + cs.start_next_cycle() + + cs.process_event(SERVER, Response) + cs.process_event(SERVER, EndOfMessage) + + cs.start_next_cycle() + assert cs.states == {CLIENT: IDLE, SERVER: IDLE} + + # No keepalive + + cs.process_event(CLIENT, Request) + cs.process_keep_alive_disabled() + cs.process_event(CLIENT, EndOfMessage) + cs.process_event(SERVER, Response) + cs.process_event(SERVER, EndOfMessage) + + with pytest.raises(LocalProtocolError): + cs.start_next_cycle() + + # One side closed + + cs = ConnectionState() + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + cs.process_event(CLIENT, ConnectionClosed) + cs.process_event(SERVER, Response) + cs.process_event(SERVER, EndOfMessage) + + with pytest.raises(LocalProtocolError): + cs.start_next_cycle() + + # Succesful protocol switch + + cs = ConnectionState() + cs.process_client_switch_proposal(_SWITCH_UPGRADE) + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + cs.process_event(SERVER, InformationalResponse, _SWITCH_UPGRADE) + + with pytest.raises(LocalProtocolError): + cs.start_next_cycle() + + # Failed protocol switch + + cs = ConnectionState() + cs.process_client_switch_proposal(_SWITCH_UPGRADE) + cs.process_event(CLIENT, Request) + cs.process_event(CLIENT, EndOfMessage) + cs.process_event(SERVER, Response) + cs.process_event(SERVER, EndOfMessage) + + cs.start_next_cycle() + assert cs.states == {CLIENT: IDLE, SERVER: IDLE} + + +def test_server_request_is_illegal() -> None: + # There used to be a bug in how we handled the Request special case that + # made this allowed... + cs = ConnectionState() + with pytest.raises(LocalProtocolError): + cs.process_event(SERVER, Request) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_util.py b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_util.py new file mode 100644 index 0000000000000000000000000000000000000000..79bc095185c79313b238fb034ef746c7f67b9d93 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/h11/tests/test_util.py @@ -0,0 +1,112 @@ +import re +import sys +import traceback +from typing import NoReturn + +import pytest + +from .._util import ( + bytesify, + LocalProtocolError, + ProtocolError, + RemoteProtocolError, + Sentinel, + validate, +) + + +def test_ProtocolError() -> None: + with pytest.raises(TypeError): + ProtocolError("abstract base class") + + +def test_LocalProtocolError() -> None: + try: + raise LocalProtocolError("foo") + except LocalProtocolError as e: + assert str(e) == "foo" + assert e.error_status_hint == 400 + + try: + raise LocalProtocolError("foo", error_status_hint=418) + except LocalProtocolError as e: + assert str(e) == "foo" + assert e.error_status_hint == 418 + + def thunk() -> NoReturn: + raise LocalProtocolError("a", error_status_hint=420) + + try: + try: + thunk() + except LocalProtocolError as exc1: + orig_traceback = "".join(traceback.format_tb(sys.exc_info()[2])) + exc1._reraise_as_remote_protocol_error() + except RemoteProtocolError as exc2: + assert type(exc2) is RemoteProtocolError + assert exc2.args == ("a",) + assert exc2.error_status_hint == 420 + new_traceback = "".join(traceback.format_tb(sys.exc_info()[2])) + assert new_traceback.endswith(orig_traceback) + + +def test_validate() -> None: + my_re = re.compile(rb"(?P[0-9]+)\.(?P[0-9]+)") + with pytest.raises(LocalProtocolError): + validate(my_re, b"0.") + + groups = validate(my_re, b"0.1") + assert groups == {"group1": b"0", "group2": b"1"} + + # successful partial matches are an error - must match whole string + with pytest.raises(LocalProtocolError): + validate(my_re, b"0.1xx") + with pytest.raises(LocalProtocolError): + validate(my_re, b"0.1\n") + + +def test_validate_formatting() -> None: + my_re = re.compile(rb"foo") + + with pytest.raises(LocalProtocolError) as excinfo: + validate(my_re, b"", "oops") + assert "oops" in str(excinfo.value) + + with pytest.raises(LocalProtocolError) as excinfo: + validate(my_re, b"", "oops {}") + assert "oops {}" in str(excinfo.value) + + with pytest.raises(LocalProtocolError) as excinfo: + validate(my_re, b"", "oops {} xx", 10) + assert "oops 10 xx" in str(excinfo.value) + + +def test_make_sentinel() -> None: + class S(Sentinel, metaclass=Sentinel): + pass + + assert repr(S) == "S" + assert S == S + assert type(S).__name__ == "S" + assert S in {S} + assert type(S) is S + + class S2(Sentinel, metaclass=Sentinel): + pass + + assert repr(S2) == "S2" + assert S != S2 + assert S not in {S2} + assert type(S) is not type(S2) + + +def test_bytesify() -> None: + assert bytesify(b"123") == b"123" + assert bytesify(bytearray(b"123")) == b"123" + assert bytesify("123") == b"123" + + with pytest.raises(UnicodeEncodeError): + bytesify("\u1234") + + with pytest.raises(TypeError): + bytesify(10) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/imageio-2.37.2.dist-info/licenses/LICENSE b/URSA/.venv_ursa/lib/python3.12/site-packages/imageio-2.37.2.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..5ca6fd6c5f24a0ff3c25c3085bc455fc0395c42c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/imageio-2.37.2.dist-info/licenses/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2014-2025, imageio developers +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbfc037555d2be6f744e14a51355476c80eba360 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f123453688eb497db3acaa28f542b05d0e8c316 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/_identifier.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1e153e9b28c7f093214c5dcb4e065bc9821036d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/async_utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..10971944cd2925304eebc845f4600bbe4742b999 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/bccache.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c8a5741c4f4e7ddfe26f939274d5585930a7358 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/constants.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a7a629c04e6bb5502ce40b59cfb2cb5f9a0a1ae Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/debug.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..007bee370721fd4791074297fbcd8399eb114abf Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/defaults.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b307f5ee4b5fe4fb64f945f82075687698966e2f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/environment.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7ad59c2f01581841832c774ad5f74e03ad8a5a1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/exceptions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cb2944467a4ca455b1589c8b041719d1b8b7577 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/ext.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..28fe7ee00e419d32ee895566dfcf1d6ecf45a434 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/filters.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45b2cd163934ee7f58b5279e7cb84c40516aefbc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/idtracking.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..829c2717c200748ada4b7c3e68c12977bef07f1f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/lexer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abcc24af7af4a30a9bc49607d31373ac874ddd16 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/loaders.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/meta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/meta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1c41d57a78cad894f89d28ddac01253054d8304 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/meta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c13e5c3b097e0d66da0549a0a32362f5c017a80f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nativetypes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..745c6826dc301f6cc580de12b9e68081b114ae46 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/nodes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfa2d2794c90cd785a5a7a85bbb104c5d6520304 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/optimizer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56307ce0d3fcc5030678698f3f2f5d9980365964 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/parser.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b273d235f3f12169422a5aaafd580a639793aced Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/runtime.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17bad4288f82af8bb369ad9025635f6a640c2730 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/sandbox.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7357c4b668fba85adb6e74aec46353f49ee6afb5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/tests.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33ada410115ccafc5923fa9d5fd2ea8300b195e7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ed21f1c1b328a614c903a2f40957985800fc2d3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/jinja2/__pycache__/visitor.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..752a4f5d292f67b4d7c0ade3feee8752221d8a9a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ffd88846ab079fa57d2a09996df8e8d0ba4dcfb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..304f1182c7725c178f281f9ee9ace28d2c6622be Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_base.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f63b939dfeb44919397049206bdb1bfc4b0bd5b5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_base.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_fp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_fp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..592b4557bfef8b93c484c61708e9bb16eb34deb3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_fp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_iv.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_iv.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f7978544a6530b0174c60d3d4e8a4370f5eb443 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_iv.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa7ff9c00e6899f26f1ab4291d1ca5969299b7a5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp_python.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp_python.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4936e2cd1396cf494ccb663a10c1dd038b4462c8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/ctx_mp_python.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/identification.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/identification.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89eb7047d535e864d95329178958622ce03aac40 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/identification.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/math2.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/math2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6760dcd35b49d82885457a550bce2be74806d81a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/math2.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/rational.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/rational.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77950a62a42bb1597098ac24f90ae7be536196c5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/rational.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/usertools.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/usertools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4dbd4aa2a681d1bd84d29f9152dddf9af8f28181 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/usertools.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/visualization.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/visualization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..092d04b8f1b6b77a635ee5ac2489cf6841e8632e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/__pycache__/visualization.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..040a3806b968f75b8d1a88ae37a4979fe83d466a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__init__.py @@ -0,0 +1,6 @@ +from . import calculus +# XXX: hack to set methods +from . import approximation +from . import differentiation +from . import extrapolation +from . import polynomials diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1aca57c7b273da53e5dbe3e8b3e2843bf06cef5d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/approximation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/approximation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b055e19089ff34d9fdd9c9423a0fd74998e28e8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/approximation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/calculus.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/calculus.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a2982a479c79babddb107a69c5a99f7c8290218 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/calculus.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/differentiation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/differentiation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4cddb1a41bc78965e4738d843e328139eae40e8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/differentiation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/extrapolation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/extrapolation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfdf68c0949ce208c12a8ede1badaf3cd746be5d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/extrapolation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/inverselaplace.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/inverselaplace.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..73f763010fc327673943853a3bc39de12b21b0f9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/inverselaplace.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/odes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/odes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bde06848327e12d3a7a45e568ec16da9dda0095 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/odes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/optimization.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/optimization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfaf6454eb2f9e8753056438a7f915bdd5f7d231 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/optimization.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/polynomials.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/polynomials.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..351f8975624a43e7f2fb99035d8383df82b870c5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/polynomials.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/quadrature.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/quadrature.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d624e050f352aa79c9f3ba0cf1d39dcc4cf5d8f9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/__pycache__/quadrature.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/approximation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/approximation.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca5cc598fb53491cb6ae4a41a40477c58544d53 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/approximation.py @@ -0,0 +1,246 @@ +from ..libmp.backend import xrange +from .calculus import defun + +#----------------------------------------------------------------------------# +# Approximation methods # +#----------------------------------------------------------------------------# + +# The Chebyshev approximation formula is given at: +# http://mathworld.wolfram.com/ChebyshevApproximationFormula.html + +# The only major changes in the following code is that we return the +# expanded polynomial coefficients instead of Chebyshev coefficients, +# and that we automatically transform [a,b] -> [-1,1] and back +# for convenience. + +# Coefficient in Chebyshev approximation +def chebcoeff(ctx,f,a,b,j,N): + s = ctx.mpf(0) + h = ctx.mpf(0.5) + for k in range(1, N+1): + t = ctx.cospi((k-h)/N) + s += f(t*(b-a)*h + (b+a)*h) * ctx.cospi(j*(k-h)/N) + return 2*s/N + +# Generate Chebyshev polynomials T_n(ax+b) in expanded form +def chebT(ctx, a=1, b=0): + Tb = [1] + yield Tb + Ta = [b, a] + while 1: + yield Ta + # Recurrence: T[n+1](ax+b) = 2*(ax+b)*T[n](ax+b) - T[n-1](ax+b) + Tmp = [0] + [2*a*t for t in Ta] + for i, c in enumerate(Ta): Tmp[i] += 2*b*c + for i, c in enumerate(Tb): Tmp[i] -= c + Ta, Tb = Tmp, Ta + +@defun +def chebyfit(ctx, f, interval, N, error=False): + r""" + Computes a polynomial of degree `N-1` that approximates the + given function `f` on the interval `[a, b]`. With ``error=True``, + :func:`~mpmath.chebyfit` also returns an accurate estimate of the + maximum absolute error; that is, the maximum value of + `|f(x) - P(x)|` for `x \in [a, b]`. + + :func:`~mpmath.chebyfit` uses the Chebyshev approximation formula, + which gives a nearly optimal solution: that is, the maximum + error of the approximating polynomial is very close to + the smallest possible for any polynomial of the same degree. + + Chebyshev approximation is very useful if one needs repeated + evaluation of an expensive function, such as function defined + implicitly by an integral or a differential equation. (For + example, it could be used to turn a slow mpmath function + into a fast machine-precision version of the same.) + + **Examples** + + Here we use :func:`~mpmath.chebyfit` to generate a low-degree approximation + of `f(x) = \cos(x)`, valid on the interval `[1, 2]`:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> poly, err = chebyfit(cos, [1, 2], 5, error=True) + >>> nprint(poly) + [0.00291682, 0.146166, -0.732491, 0.174141, 0.949553] + >>> nprint(err, 12) + 1.61351758081e-5 + + The polynomial can be evaluated using ``polyval``:: + + >>> nprint(polyval(poly, 1.6), 12) + -0.0291858904138 + >>> nprint(cos(1.6), 12) + -0.0291995223013 + + Sampling the true error at 1000 points shows that the error + estimate generated by ``chebyfit`` is remarkably good:: + + >>> error = lambda x: abs(cos(x) - polyval(poly, x)) + >>> nprint(max([error(1+n/1000.) for n in range(1000)]), 12) + 1.61349954245e-5 + + **Choice of degree** + + The degree `N` can be set arbitrarily high, to obtain an + arbitrarily good approximation. As a rule of thumb, an + `N`-term Chebyshev approximation is good to `N/(b-a)` decimal + places on a unit interval (although this depends on how + well-behaved `f` is). The cost grows accordingly: ``chebyfit`` + evaluates the function `(N^2)/2` times to compute the + coefficients and an additional `N` times to estimate the error. + + **Possible issues** + + One should be careful to use a sufficiently high working + precision both when calling ``chebyfit`` and when evaluating + the resulting polynomial, as the polynomial is sometimes + ill-conditioned. It is for example difficult to reach + 15-digit accuracy when evaluating the polynomial using + machine precision floats, no matter the theoretical + accuracy of the polynomial. (The option to return the + coefficients in Chebyshev form should be made available + in the future.) + + It is important to note the Chebyshev approximation works + poorly if `f` is not smooth. A function containing singularities, + rapid oscillation, etc can be approximated more effectively by + multiplying it by a weight function that cancels out the + nonsmooth features, or by dividing the interval into several + segments. + """ + a, b = ctx._as_points(interval) + orig = ctx.prec + try: + ctx.prec = orig + int(N**0.5) + 20 + c = [chebcoeff(ctx,f,a,b,k,N) for k in range(N)] + d = [ctx.zero] * N + d[0] = -c[0]/2 + h = ctx.mpf(0.5) + T = chebT(ctx, ctx.mpf(2)/(b-a), ctx.mpf(-1)*(b+a)/(b-a)) + for (k, Tk) in zip(range(N), T): + for i in range(len(Tk)): + d[i] += c[k]*Tk[i] + d = d[::-1] + # Estimate maximum error + err = ctx.zero + for k in range(N): + x = ctx.cos(ctx.pi*k/N) * (b-a)*h + (b+a)*h + err = max(err, abs(f(x) - ctx.polyval(d, x))) + finally: + ctx.prec = orig + if error: + return d, +err + else: + return d + +@defun +def fourier(ctx, f, interval, N): + r""" + Computes the Fourier series of degree `N` of the given function + on the interval `[a, b]`. More precisely, :func:`~mpmath.fourier` returns + two lists `(c, s)` of coefficients (the cosine series and sine + series, respectively), such that + + .. math :: + + f(x) \sim \sum_{k=0}^N + c_k \cos(k m x) + s_k \sin(k m x) + + where `m = 2 \pi / (b-a)`. + + Note that many texts define the first coefficient as `2 c_0` instead + of `c_0`. The easiest way to evaluate the computed series correctly + is to pass it to :func:`~mpmath.fourierval`. + + **Examples** + + The function `f(x) = x` has a simple Fourier series on the standard + interval `[-\pi, \pi]`. The cosine coefficients are all zero (because + the function has odd symmetry), and the sine coefficients are + rational numbers:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> c, s = fourier(lambda x: x, [-pi, pi], 5) + >>> nprint(c) + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + >>> nprint(s) + [0.0, 2.0, -1.0, 0.666667, -0.5, 0.4] + + This computes a Fourier series of a nonsymmetric function on + a nonstandard interval:: + + >>> I = [-1, 1.5] + >>> f = lambda x: x**2 - 4*x + 1 + >>> cs = fourier(f, I, 4) + >>> nprint(cs[0]) + [0.583333, 1.12479, -1.27552, 0.904708, -0.441296] + >>> nprint(cs[1]) + [0.0, -2.6255, 0.580905, 0.219974, -0.540057] + + It is instructive to plot a function along with its truncated + Fourier series:: + + >>> plot([f, lambda x: fourierval(cs, I, x)], I) #doctest: +SKIP + + Fourier series generally converge slowly (and may not converge + pointwise). For example, if `f(x) = \cosh(x)`, a 10-term Fourier + series gives an `L^2` error corresponding to 2-digit accuracy:: + + >>> I = [-1, 1] + >>> cs = fourier(cosh, I, 9) + >>> g = lambda x: (cosh(x) - fourierval(cs, I, x))**2 + >>> nprint(sqrt(quad(g, I))) + 0.00467963 + + :func:`~mpmath.fourier` uses numerical quadrature. For nonsmooth functions, + the accuracy (and speed) can be improved by including all singular + points in the interval specification:: + + >>> nprint(fourier(abs, [-1, 1], 0), 10) + ([0.5000441648], [0.0]) + >>> nprint(fourier(abs, [-1, 0, 1], 0), 10) + ([0.5], [0.0]) + + """ + interval = ctx._as_points(interval) + a = interval[0] + b = interval[-1] + L = b-a + cos_series = [] + sin_series = [] + cutoff = ctx.eps*10 + for n in xrange(N+1): + m = 2*n*ctx.pi/L + an = 2*ctx.quadgl(lambda t: f(t)*ctx.cos(m*t), interval)/L + bn = 2*ctx.quadgl(lambda t: f(t)*ctx.sin(m*t), interval)/L + if n == 0: + an /= 2 + if abs(an) < cutoff: an = ctx.zero + if abs(bn) < cutoff: bn = ctx.zero + cos_series.append(an) + sin_series.append(bn) + return cos_series, sin_series + +@defun +def fourierval(ctx, series, interval, x): + """ + Evaluates a Fourier series (in the format computed by + by :func:`~mpmath.fourier` for the given interval) at the point `x`. + + The series should be a pair `(c, s)` where `c` is the + cosine series and `s` is the sine series. The two lists + need not have the same length. + """ + cs, ss = series + ab = ctx._as_points(interval) + a = interval[0] + b = interval[-1] + m = 2*ctx.pi/(ab[-1]-ab[0]) + s = ctx.zero + s += ctx.fsum(cs[n]*ctx.cos(m*n*x) for n in xrange(len(cs)) if cs[n]) + s += ctx.fsum(ss[n]*ctx.sin(m*n*x) for n in xrange(len(ss)) if ss[n]) + return s diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/calculus.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/calculus.py new file mode 100644 index 0000000000000000000000000000000000000000..24256f121d6c07e5ce954f2a5f5024f156f64016 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/calculus.py @@ -0,0 +1,6 @@ +class CalculusMethods(object): + pass + +def defun(f): + setattr(CalculusMethods, f.__name__, f) + return f diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/differentiation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/differentiation.py new file mode 100644 index 0000000000000000000000000000000000000000..f8186bebd4476476eded9e4b2f8dc3eb23ef5ff9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/differentiation.py @@ -0,0 +1,647 @@ +from ..libmp.backend import xrange +from .calculus import defun + +try: + iteritems = dict.iteritems +except AttributeError: + iteritems = dict.items + +#----------------------------------------------------------------------------# +# Differentiation # +#----------------------------------------------------------------------------# + +@defun +def difference(ctx, s, n): + r""" + Given a sequence `(s_k)` containing at least `n+1` items, returns the + `n`-th forward difference, + + .. math :: + + \Delta^n = \sum_{k=0}^{\infty} (-1)^{k+n} {n \choose k} s_k. + """ + n = int(n) + d = ctx.zero + b = (-1) ** (n & 1) + for k in xrange(n+1): + d += b * s[k] + b = (b * (k-n)) // (k+1) + return d + +def hsteps(ctx, f, x, n, prec, **options): + singular = options.get('singular') + addprec = options.get('addprec', 10) + direction = options.get('direction', 0) + workprec = (prec+2*addprec) * (n+1) + orig = ctx.prec + try: + ctx.prec = workprec + h = options.get('h') + if h is None: + if options.get('relative'): + hextramag = int(ctx.mag(x)) + else: + hextramag = 0 + h = ctx.ldexp(1, -prec-addprec-hextramag) + else: + h = ctx.convert(h) + # Directed: steps x, x+h, ... x+n*h + direction = options.get('direction', 0) + if direction: + h *= ctx.sign(direction) + steps = xrange(n+1) + norm = h + # Central: steps x-n*h, x-(n-2)*h ..., x, ..., x+(n-2)*h, x+n*h + else: + steps = xrange(-n, n+1, 2) + norm = (2*h) + # Perturb + if singular: + x += 0.5*h + values = [f(x+k*h) for k in steps] + return values, norm, workprec + finally: + ctx.prec = orig + + +@defun +def diff(ctx, f, x, n=1, **options): + r""" + Numerically computes the derivative of `f`, `f'(x)`, or generally for + an integer `n \ge 0`, the `n`-th derivative `f^{(n)}(x)`. + A few basic examples are:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> diff(lambda x: x**2 + x, 1.0) + 3.0 + >>> diff(lambda x: x**2 + x, 1.0, 2) + 2.0 + >>> diff(lambda x: x**2 + x, 1.0, 3) + 0.0 + >>> nprint([diff(exp, 3, n) for n in range(5)]) # exp'(x) = exp(x) + [20.0855, 20.0855, 20.0855, 20.0855, 20.0855] + + Even more generally, given a tuple of arguments `(x_1, \ldots, x_k)` + and order `(n_1, \ldots, n_k)`, the partial derivative + `f^{(n_1,\ldots,n_k)}(x_1,\ldots,x_k)` is evaluated. For example:: + + >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (0,1)) + 2.75 + >>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (1,1)) + 3.0 + + **Options** + + The following optional keyword arguments are recognized: + + ``method`` + Supported methods are ``'step'`` or ``'quad'``: derivatives may be + computed using either a finite difference with a small step + size `h` (default), or numerical quadrature. + ``direction`` + Direction of finite difference: can be -1 for a left + difference, 0 for a central difference (default), or +1 + for a right difference; more generally can be any complex number. + ``addprec`` + Extra precision for `h` used to account for the function's + sensitivity to perturbations (default = 10). + ``relative`` + Choose `h` relative to the magnitude of `x`, rather than an + absolute value; useful for large or tiny `x` (default = False). + ``h`` + As an alternative to ``addprec`` and ``relative``, manually + select the step size `h`. + ``singular`` + If True, evaluation exactly at the point `x` is avoided; this is + useful for differentiating functions with removable singularities. + Default = False. + ``radius`` + Radius of integration contour (with ``method = 'quad'``). + Default = 0.25. A larger radius typically is faster and more + accurate, but it must be chosen so that `f` has no + singularities within the radius from the evaluation point. + + A finite difference requires `n+1` function evaluations and must be + performed at `(n+1)` times the target precision. Accordingly, `f` must + support fast evaluation at high precision. + + With integration, a larger number of function evaluations is + required, but not much extra precision is required. For high order + derivatives, this method may thus be faster if f is very expensive to + evaluate at high precision. + + **Further examples** + + The direction option is useful for computing left- or right-sided + derivatives of nonsmooth functions:: + + >>> diff(abs, 0, direction=0) + 0.0 + >>> diff(abs, 0, direction=1) + 1.0 + >>> diff(abs, 0, direction=-1) + -1.0 + + More generally, if the direction is nonzero, a right difference + is computed where the step size is multiplied by sign(direction). + For example, with direction=+j, the derivative from the positive + imaginary direction will be computed:: + + >>> diff(abs, 0, direction=j) + (0.0 - 1.0j) + + With integration, the result may have a small imaginary part + even even if the result is purely real:: + + >>> diff(sqrt, 1, method='quad') # doctest:+ELLIPSIS + (0.5 - 4.59...e-26j) + >>> chop(_) + 0.5 + + Adding precision to obtain an accurate value:: + + >>> diff(cos, 1e-30) + 0.0 + >>> diff(cos, 1e-30, h=0.0001) + -9.99999998328279e-31 + >>> diff(cos, 1e-30, addprec=100) + -1.0e-30 + + """ + partial = False + try: + orders = list(n) + x = list(x) + partial = True + except TypeError: + pass + if partial: + x = [ctx.convert(_) for _ in x] + return _partial_diff(ctx, f, x, orders, options) + method = options.get('method', 'step') + if n == 0 and method != 'quad' and not options.get('singular'): + return f(ctx.convert(x)) + prec = ctx.prec + try: + if method == 'step': + values, norm, workprec = hsteps(ctx, f, x, n, prec, **options) + ctx.prec = workprec + v = ctx.difference(values, n) / norm**n + elif method == 'quad': + ctx.prec += 10 + radius = ctx.convert(options.get('radius', 0.25)) + def g(t): + rei = radius*ctx.expj(t) + z = x + rei + return f(z) / rei**n + d = ctx.quadts(g, [0, 2*ctx.pi]) + v = d * ctx.factorial(n) / (2*ctx.pi) + else: + raise ValueError("unknown method: %r" % method) + finally: + ctx.prec = prec + return +v + +def _partial_diff(ctx, f, xs, orders, options): + if not orders: + return f() + if not sum(orders): + return f(*xs) + i = 0 + for i in range(len(orders)): + if orders[i]: + break + order = orders[i] + def fdiff_inner(*f_args): + def inner(t): + return f(*(f_args[:i] + (t,) + f_args[i+1:])) + return ctx.diff(inner, f_args[i], order, **options) + orders[i] = 0 + return _partial_diff(ctx, fdiff_inner, xs, orders, options) + +@defun +def diffs(ctx, f, x, n=None, **options): + r""" + Returns a generator that yields the sequence of derivatives + + .. math :: + + f(x), f'(x), f''(x), \ldots, f^{(k)}(x), \ldots + + With ``method='step'``, :func:`~mpmath.diffs` uses only `O(k)` + function evaluations to generate the first `k` derivatives, + rather than the roughly `O(k^2)` evaluations + required if one calls :func:`~mpmath.diff` `k` separate times. + + With `n < \infty`, the generator stops as soon as the + `n`-th derivative has been generated. If the exact number of + needed derivatives is known in advance, this is further + slightly more efficient. + + Options are the same as for :func:`~mpmath.diff`. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15 + >>> nprint(list(diffs(cos, 1, 5))) + [0.540302, -0.841471, -0.540302, 0.841471, 0.540302, -0.841471] + >>> for i, d in zip(range(6), diffs(cos, 1)): + ... print("%s %s" % (i, d)) + ... + 0 0.54030230586814 + 1 -0.841470984807897 + 2 -0.54030230586814 + 3 0.841470984807897 + 4 0.54030230586814 + 5 -0.841470984807897 + + """ + if n is None: + n = ctx.inf + else: + n = int(n) + if options.get('method', 'step') != 'step': + k = 0 + while k < n + 1: + yield ctx.diff(f, x, k, **options) + k += 1 + return + singular = options.get('singular') + if singular: + yield ctx.diff(f, x, 0, singular=True) + else: + yield f(ctx.convert(x)) + if n < 1: + return + if n == ctx.inf: + A, B = 1, 2 + else: + A, B = 1, n+1 + while 1: + callprec = ctx.prec + y, norm, workprec = hsteps(ctx, f, x, B, callprec, **options) + for k in xrange(A, B): + try: + ctx.prec = workprec + d = ctx.difference(y, k) / norm**k + finally: + ctx.prec = callprec + yield +d + if k >= n: + return + A, B = B, int(A*1.4+1) + B = min(B, n) + +def iterable_to_function(gen): + gen = iter(gen) + data = [] + def f(k): + for i in xrange(len(data), k+1): + data.append(next(gen)) + return data[k] + return f + +@defun +def diffs_prod(ctx, factors): + r""" + Given a list of `N` iterables or generators yielding + `f_k(x), f'_k(x), f''_k(x), \ldots` for `k = 1, \ldots, N`, + generate `g(x), g'(x), g''(x), \ldots` where + `g(x) = f_1(x) f_2(x) \cdots f_N(x)`. + + At high precision and for large orders, this is typically more efficient + than numerical differentiation if the derivatives of each `f_k(x)` + admit direct computation. + + Note: This function does not increase the working precision internally, + so guard digits may have to be added externally for full accuracy. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> f = lambda x: exp(x)*cos(x)*sin(x) + >>> u = diffs(f, 1) + >>> v = mp.diffs_prod([diffs(exp,1), diffs(cos,1), diffs(sin,1)]) + >>> next(u); next(v) + 1.23586333600241 + 1.23586333600241 + >>> next(u); next(v) + 0.104658952245596 + 0.104658952245596 + >>> next(u); next(v) + -5.96999877552086 + -5.96999877552086 + >>> next(u); next(v) + -12.4632923122697 + -12.4632923122697 + + """ + N = len(factors) + if N == 1: + for c in factors[0]: + yield c + else: + u = iterable_to_function(ctx.diffs_prod(factors[:N//2])) + v = iterable_to_function(ctx.diffs_prod(factors[N//2:])) + n = 0 + while 1: + #yield sum(binomial(n,k)*u(n-k)*v(k) for k in xrange(n+1)) + s = u(n) * v(0) + a = 1 + for k in xrange(1,n+1): + a = a * (n-k+1) // k + s += a * u(n-k) * v(k) + yield s + n += 1 + +def dpoly(n, _cache={}): + """ + nth differentiation polynomial for exp (Faa di Bruno's formula). + + TODO: most exponents are zero, so maybe a sparse representation + would be better. + """ + if n in _cache: + return _cache[n] + if not _cache: + _cache[0] = {(0,):1} + R = dpoly(n-1) + R = dict((c+(0,),v) for (c,v) in iteritems(R)) + Ra = {} + for powers, count in iteritems(R): + powers1 = (powers[0]+1,) + powers[1:] + if powers1 in Ra: + Ra[powers1] += count + else: + Ra[powers1] = count + for powers, count in iteritems(R): + if not sum(powers): + continue + for k,p in enumerate(powers): + if p: + powers2 = powers[:k] + (p-1,powers[k+1]+1) + powers[k+2:] + if powers2 in Ra: + Ra[powers2] += p*count + else: + Ra[powers2] = p*count + _cache[n] = Ra + return _cache[n] + +@defun +def diffs_exp(ctx, fdiffs): + r""" + Given an iterable or generator yielding `f(x), f'(x), f''(x), \ldots` + generate `g(x), g'(x), g''(x), \ldots` where `g(x) = \exp(f(x))`. + + At high precision and for large orders, this is typically more efficient + than numerical differentiation if the derivatives of `f(x)` + admit direct computation. + + Note: This function does not increase the working precision internally, + so guard digits may have to be added externally for full accuracy. + + **Examples** + + The derivatives of the gamma function can be computed using + logarithmic differentiation:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> + >>> def diffs_loggamma(x): + ... yield loggamma(x) + ... i = 0 + ... while 1: + ... yield psi(i,x) + ... i += 1 + ... + >>> u = diffs_exp(diffs_loggamma(3)) + >>> v = diffs(gamma, 3) + >>> next(u); next(v) + 2.0 + 2.0 + >>> next(u); next(v) + 1.84556867019693 + 1.84556867019693 + >>> next(u); next(v) + 2.49292999190269 + 2.49292999190269 + >>> next(u); next(v) + 3.44996501352367 + 3.44996501352367 + + """ + fn = iterable_to_function(fdiffs) + f0 = ctx.exp(fn(0)) + yield f0 + i = 1 + while 1: + s = ctx.mpf(0) + for powers, c in iteritems(dpoly(i)): + s += c*ctx.fprod(fn(k+1)**p for (k,p) in enumerate(powers) if p) + yield s * f0 + i += 1 + +@defun +def differint(ctx, f, x, n=1, x0=0): + r""" + Calculates the Riemann-Liouville differintegral, or fractional + derivative, defined by + + .. math :: + + \,_{x_0}{\mathbb{D}}^n_xf(x) = \frac{1}{\Gamma(m-n)} \frac{d^m}{dx^m} + \int_{x_0}^{x}(x-t)^{m-n-1}f(t)dt + + where `f` is a given (presumably well-behaved) function, + `x` is the evaluation point, `n` is the order, and `x_0` is + the reference point of integration (`m` is an arbitrary + parameter selected automatically). + + With `n = 1`, this is just the standard derivative `f'(x)`; with `n = 2`, + the second derivative `f''(x)`, etc. With `n = -1`, it gives + `\int_{x_0}^x f(t) dt`, with `n = -2` + it gives `\int_{x_0}^x \left( \int_{x_0}^t f(u) du \right) dt`, etc. + + As `n` is permitted to be any number, this operator generalizes + iterated differentiation and iterated integration to a single + operator with a continuous order parameter. + + **Examples** + + There is an exact formula for the fractional derivative of a + monomial `x^p`, which may be used as a reference. For example, + the following gives a half-derivative (order 0.5):: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> x = mpf(3); p = 2; n = 0.5 + >>> differint(lambda t: t**p, x, n) + 7.81764019044672 + >>> gamma(p+1)/gamma(p-n+1) * x**(p-n) + 7.81764019044672 + + Another useful test function is the exponential function, whose + integration / differentiation formula easy generalizes + to arbitrary order. Here we first compute a third derivative, + and then a triply nested integral. (The reference point `x_0` + is set to `-\infty` to avoid nonzero endpoint terms.):: + + >>> differint(lambda x: exp(pi*x), -1.5, 3) + 0.278538406900792 + >>> exp(pi*-1.5) * pi**3 + 0.278538406900792 + >>> differint(lambda x: exp(pi*x), 3.5, -3, -inf) + 1922.50563031149 + >>> exp(pi*3.5) / pi**3 + 1922.50563031149 + + However, for noninteger `n`, the differentiation formula for the + exponential function must be modified to give the same result as the + Riemann-Liouville differintegral:: + + >>> x = mpf(3.5) + >>> c = pi + >>> n = 1+2*j + >>> differint(lambda x: exp(c*x), x, n) + (-123295.005390743 + 140955.117867654j) + >>> x**(-n) * exp(c)**x * (x*c)**n * gammainc(-n, 0, x*c) / gamma(-n) + (-123295.005390743 + 140955.117867654j) + + + """ + m = max(int(ctx.ceil(ctx.re(n)))+1, 1) + r = m-n-1 + g = lambda x: ctx.quad(lambda t: (x-t)**r * f(t), [x0, x]) + return ctx.diff(g, x, m) / ctx.gamma(m-n) + +@defun +def diffun(ctx, f, n=1, **options): + r""" + Given a function `f`, returns a function `g(x)` that evaluates the nth + derivative `f^{(n)}(x)`:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> cos2 = diffun(sin) + >>> sin2 = diffun(sin, 4) + >>> cos(1.3), cos2(1.3) + (0.267498828624587, 0.267498828624587) + >>> sin(1.3), sin2(1.3) + (0.963558185417193, 0.963558185417193) + + The function `f` must support arbitrary precision evaluation. + See :func:`~mpmath.diff` for additional details and supported + keyword options. + """ + if n == 0: + return f + def g(x): + return ctx.diff(f, x, n, **options) + return g + +@defun +def taylor(ctx, f, x, n, **options): + r""" + Produces a degree-`n` Taylor polynomial around the point `x` of the + given function `f`. The coefficients are returned as a list. + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> nprint(chop(taylor(sin, 0, 5))) + [0.0, 1.0, 0.0, -0.166667, 0.0, 0.00833333] + + The coefficients are computed using high-order numerical + differentiation. The function must be possible to evaluate + to arbitrary precision. See :func:`~mpmath.diff` for additional details + and supported keyword options. + + Note that to evaluate the Taylor polynomial as an approximation + of `f`, e.g. with :func:`~mpmath.polyval`, the coefficients must be reversed, + and the point of the Taylor expansion must be subtracted from + the argument: + + >>> p = taylor(exp, 2.0, 10) + >>> polyval(p[::-1], 2.5 - 2.0) + 12.1824939606092 + >>> exp(2.5) + 12.1824939607035 + + """ + gen = enumerate(ctx.diffs(f, x, n, **options)) + if options.get("chop", True): + return [ctx.chop(d)/ctx.factorial(i) for i, d in gen] + else: + return [d/ctx.factorial(i) for i, d in gen] + +@defun +def pade(ctx, a, L, M): + r""" + Computes a Pade approximation of degree `(L, M)` to a function. + Given at least `L+M+1` Taylor coefficients `a` approximating + a function `A(x)`, :func:`~mpmath.pade` returns coefficients of + polynomials `P, Q` satisfying + + .. math :: + + P = \sum_{k=0}^L p_k x^k + + Q = \sum_{k=0}^M q_k x^k + + Q_0 = 1 + + A(x) Q(x) = P(x) + O(x^{L+M+1}) + + `P(x)/Q(x)` can provide a good approximation to an analytic function + beyond the radius of convergence of its Taylor series (example + from G.A. Baker 'Essentials of Pade Approximants' Academic Press, + Ch.1A):: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> one = mpf(1) + >>> def f(x): + ... return sqrt((one + 2*x)/(one + x)) + ... + >>> a = taylor(f, 0, 6) + >>> p, q = pade(a, 3, 3) + >>> x = 10 + >>> polyval(p[::-1], x)/polyval(q[::-1], x) + 1.38169105566806 + >>> f(x) + 1.38169855941551 + + """ + # To determine L+1 coefficients of P and M coefficients of Q + # L+M+1 coefficients of A must be provided + if len(a) < L+M+1: + raise ValueError("L+M+1 Coefficients should be provided") + + if M == 0: + if L == 0: + return [ctx.one], [ctx.one] + else: + return a[:L+1], [ctx.one] + + # Solve first + # a[L]*q[1] + ... + a[L-M+1]*q[M] = -a[L+1] + # ... + # a[L+M-1]*q[1] + ... + a[L]*q[M] = -a[L+M] + A = ctx.matrix(M) + for j in range(M): + for i in range(min(M, L+j+1)): + A[j, i] = a[L+j-i] + v = -ctx.matrix(a[(L+1):(L+M+1)]) + x = ctx.lu_solve(A, v) + q = [ctx.one] + list(x) + # compute p + p = [0]*(L+1) + for i in range(L+1): + s = a[i] + for j in range(1, min(M,i) + 1): + s += q[j]*a[i-j] + p[i] = s + return p, q diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/extrapolation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/extrapolation.py new file mode 100644 index 0000000000000000000000000000000000000000..7df0fea3c62c9b71ee24d3f39fd9b7fd3318ed23 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/extrapolation.py @@ -0,0 +1,2115 @@ +try: + from itertools import izip +except ImportError: + izip = zip + +from ..libmp.backend import xrange +from .calculus import defun + +try: + next = next +except NameError: + next = lambda _: _.next() + +@defun +def richardson(ctx, seq): + r""" + Given a list ``seq`` of the first `N` elements of a slowly convergent + infinite sequence, :func:`~mpmath.richardson` computes the `N`-term + Richardson extrapolate for the limit. + + :func:`~mpmath.richardson` returns `(v, c)` where `v` is the estimated + limit and `c` is the magnitude of the largest weight used during the + computation. The weight provides an estimate of the precision + lost to cancellation. Due to cancellation effects, the sequence must + be typically be computed at a much higher precision than the target + accuracy of the extrapolation. + + **Applicability and issues** + + The `N`-step Richardson extrapolation algorithm used by + :func:`~mpmath.richardson` is described in [1]. + + Richardson extrapolation only works for a specific type of sequence, + namely one converging like partial sums of + `P(1)/Q(1) + P(2)/Q(2) + \ldots` where `P` and `Q` are polynomials. + When the sequence does not convergence at such a rate + :func:`~mpmath.richardson` generally produces garbage. + + Richardson extrapolation has the advantage of being fast: the `N`-term + extrapolate requires only `O(N)` arithmetic operations, and usually + produces an estimate that is accurate to `O(N)` digits. Contrast with + the Shanks transformation (see :func:`~mpmath.shanks`), which requires + `O(N^2)` operations. + + :func:`~mpmath.richardson` is unable to produce an estimate for the + approximation error. One way to estimate the error is to perform + two extrapolations with slightly different `N` and comparing the + results. + + Richardson extrapolation does not work for oscillating sequences. + As a simple workaround, :func:`~mpmath.richardson` detects if the last + three elements do not differ monotonically, and in that case + applies extrapolation only to the even-index elements. + + **Example** + + Applying Richardson extrapolation to the Leibniz series for `\pi`:: + + >>> from mpmath import * + >>> mp.dps = 30; mp.pretty = True + >>> S = [4*sum(mpf(-1)**n/(2*n+1) for n in range(m)) + ... for m in range(1,30)] + >>> v, c = richardson(S[:10]) + >>> v + 3.2126984126984126984126984127 + >>> nprint([v-pi, c]) + [0.0711058, 2.0] + + >>> v, c = richardson(S[:30]) + >>> v + 3.14159265468624052829954206226 + >>> nprint([v-pi, c]) + [1.09645e-9, 20833.3] + + **References** + + 1. [BenderOrszag]_ pp. 375-376 + + """ + if len(seq) < 3: + raise ValueError("seq should be of minimum length 3") + if ctx.sign(seq[-1]-seq[-2]) != ctx.sign(seq[-2]-seq[-3]): + seq = seq[::2] + N = len(seq)//2-1 + s = ctx.zero + # The general weight is c[k] = (N+k)**N * (-1)**(k+N) / k! / (N-k)! + # To avoid repeated factorials, we simplify the quotient + # of successive weights to obtain a recurrence relation + c = (-1)**N * N**N / ctx.mpf(ctx._ifac(N)) + maxc = 1 + for k in xrange(N+1): + s += c * seq[N+k] + maxc = max(abs(c), maxc) + c *= (k-N)*ctx.mpf(k+N+1)**N + c /= ((1+k)*ctx.mpf(k+N)**N) + return s, maxc + +@defun +def shanks(ctx, seq, table=None, randomized=False): + r""" + Given a list ``seq`` of the first `N` elements of a slowly + convergent infinite sequence `(A_k)`, :func:`~mpmath.shanks` computes the iterated + Shanks transformation `S(A), S(S(A)), \ldots, S^{N/2}(A)`. The Shanks + transformation often provides strong convergence acceleration, + especially if the sequence is oscillating. + + The iterated Shanks transformation is computed using the Wynn + epsilon algorithm (see [1]). :func:`~mpmath.shanks` returns the full + epsilon table generated by Wynn's algorithm, which can be read + off as follows: + + * The table is a list of lists forming a lower triangular matrix, + where higher row and column indices correspond to more accurate + values. + * The columns with even index hold dummy entries (required for the + computation) and the columns with odd index hold the actual + extrapolates. + * The last element in the last row is typically the most + accurate estimate of the limit. + * The difference to the third last element in the last row + provides an estimate of the approximation error. + * The magnitude of the second last element provides an estimate + of the numerical accuracy lost to cancellation. + + For convenience, so the extrapolation is stopped at an odd index + so that ``shanks(seq)[-1][-1]`` always gives an estimate of the + limit. + + Optionally, an existing table can be passed to :func:`~mpmath.shanks`. + This can be used to efficiently extend a previous computation after + new elements have been appended to the sequence. The table will + then be updated in-place. + + **The Shanks transformation** + + The Shanks transformation is defined as follows (see [2]): given + the input sequence `(A_0, A_1, \ldots)`, the transformed sequence is + given by + + .. math :: + + S(A_k) = \frac{A_{k+1}A_{k-1}-A_k^2}{A_{k+1}+A_{k-1}-2 A_k} + + The Shanks transformation gives the exact limit `A_{\infty}` in a + single step if `A_k = A + a q^k`. Note in particular that it + extrapolates the exact sum of a geometric series in a single step. + + Applying the Shanks transformation once often improves convergence + substantially for an arbitrary sequence, but the optimal effect is + obtained by applying it iteratively: + `S(S(A_k)), S(S(S(A_k))), \ldots`. + + Wynn's epsilon algorithm provides an efficient way to generate + the table of iterated Shanks transformations. It reduces the + computation of each element to essentially a single division, at + the cost of requiring dummy elements in the table. See [1] for + details. + + **Precision issues** + + Due to cancellation effects, the sequence must be typically be + computed at a much higher precision than the target accuracy + of the extrapolation. + + If the Shanks transformation converges to the exact limit (such + as if the sequence is a geometric series), then a division by + zero occurs. By default, :func:`~mpmath.shanks` handles this case by + terminating the iteration and returning the table it has + generated so far. With *randomized=True*, it will instead + replace the zero by a pseudorandom number close to zero. + (TODO: find a better solution to this problem.) + + **Examples** + + We illustrate by applying Shanks transformation to the Leibniz + series for `\pi`:: + + >>> from mpmath import * + >>> mp.dps = 50 + >>> S = [4*sum(mpf(-1)**n/(2*n+1) for n in range(m)) + ... for m in range(1,30)] + >>> + >>> T = shanks(S[:7]) + >>> for row in T: + ... nprint(row) + ... + [-0.75] + [1.25, 3.16667] + [-1.75, 3.13333, -28.75] + [2.25, 3.14524, 82.25, 3.14234] + [-2.75, 3.13968, -177.75, 3.14139, -969.937] + [3.25, 3.14271, 327.25, 3.14166, 3515.06, 3.14161] + + The extrapolated accuracy is about 4 digits, and about 4 digits + may have been lost due to cancellation:: + + >>> L = T[-1] + >>> nprint([abs(L[-1] - pi), abs(L[-1] - L[-3]), abs(L[-2])]) + [2.22532e-5, 4.78309e-5, 3515.06] + + Now we extend the computation:: + + >>> T = shanks(S[:25], T) + >>> L = T[-1] + >>> nprint([abs(L[-1] - pi), abs(L[-1] - L[-3]), abs(L[-2])]) + [3.75527e-19, 1.48478e-19, 2.96014e+17] + + The value for pi is now accurate to 18 digits. About 18 digits may + also have been lost to cancellation. + + Here is an example with a geometric series, where the convergence + is immediate (the sum is exactly 1):: + + >>> mp.dps = 15 + >>> for row in shanks([0.5, 0.75, 0.875, 0.9375, 0.96875]): + ... nprint(row) + [4.0] + [8.0, 1.0] + + **References** + + 1. [GravesMorris]_ + + 2. [BenderOrszag]_ pp. 368-375 + + """ + if len(seq) < 2: + raise ValueError("seq should be of minimum length 2") + if table: + START = len(table) + else: + START = 0 + table = [] + STOP = len(seq) - 1 + if STOP & 1: + STOP -= 1 + one = ctx.one + eps = +ctx.eps + if randomized: + from random import Random + rnd = Random() + rnd.seed(START) + for i in xrange(START, STOP): + row = [] + for j in xrange(i+1): + if j == 0: + a, b = 0, seq[i+1]-seq[i] + else: + if j == 1: + a = seq[i] + else: + a = table[i-1][j-2] + b = row[j-1] - table[i-1][j-1] + if not b: + if randomized: + b = (1 + rnd.getrandbits(10))*eps + elif i & 1: + return table[:-1] + else: + return table + row.append(a + one/b) + table.append(row) + return table + + +class levin_class: + # levin: Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) + r""" + This interface implements Levin's (nonlinear) sequence transformation for + convergence acceleration and summation of divergent series. It performs + better than the Shanks/Wynn-epsilon algorithm for logarithmic convergent + or alternating divergent series. + + Let *A* be the series we want to sum: + + .. math :: + + A = \sum_{k=0}^{\infty} a_k + + Attention: all `a_k` must be non-zero! + + Let `s_n` be the partial sums of this series: + + .. math :: + + s_n = \sum_{k=0}^n a_k. + + **Methods** + + Calling ``levin`` returns an object with the following methods. + + ``update(...)`` works with the list of individual terms `a_k` of *A*, and + ``update_step(...)`` works with the list of partial sums `s_k` of *A*: + + .. code :: + + v, e = ...update([a_0, a_1,..., a_k]) + v, e = ...update_psum([s_0, s_1,..., s_k]) + + ``step(...)`` works with the individual terms `a_k` and ``step_psum(...)`` + works with the partial sums `s_k`: + + .. code :: + + v, e = ...step(a_k) + v, e = ...step_psum(s_k) + + *v* is the current estimate for *A*, and *e* is an error estimate which is + simply the difference between the current estimate and the last estimate. + One should not mix ``update``, ``update_psum``, ``step`` and ``step_psum``. + + **A word of caution** + + One can only hope for good results (i.e. convergence acceleration or + resummation) if the `s_n` have some well defind asymptotic behavior for + large `n` and are not erratic or random. Furthermore one usually needs very + high working precision because of the numerical cancellation. If the working + precision is insufficient, levin may produce silently numerical garbage. + Furthermore even if the Levin-transformation converges, in the general case + there is no proof that the result is mathematically sound. Only for very + special classes of problems one can prove that the Levin-transformation + converges to the expected result (for example Stieltjes-type integrals). + Furthermore the Levin-transform is quite expensive (i.e. slow) in comparison + to Shanks/Wynn-epsilon, Richardson & co. + In summary one can say that the Levin-transformation is powerful but + unreliable and that it may need a copious amount of working precision. + + The Levin transform has several variants differing in the choice of weights. + Some variants are better suited for the possible flavours of convergence + behaviour of *A* than other variants: + + .. code :: + + convergence behaviour levin-u levin-t levin-v shanks/wynn-epsilon + + logarithmic + - + - + linear + + + + + alternating divergent + + + + + + "+" means the variant is suitable,"-" means the variant is not suitable; + for comparison the Shanks/Wynn-epsilon transform is listed, too. + + The variant is controlled though the variant keyword (i.e. ``variant="u"``, + ``variant="t"`` or ``variant="v"``). Overall "u" is probably the best choice. + + Finally it is possible to use the Sidi-S transform instead of the Levin transform + by using the keyword ``method='sidi'``. The Sidi-S transform works better than the + Levin transformation for some divergent series (see the examples). + + Parameters: + + .. code :: + + method "levin" or "sidi" chooses either the Levin or the Sidi-S transformation + variant "u","t" or "v" chooses the weight variant. + + The Levin transform is also accessible through the nsum interface. + ``method="l"`` or ``method="levin"`` select the normal Levin transform while + ``method="sidi"`` + selects the Sidi-S transform. The variant is in both cases selected through the + levin_variant keyword. The stepsize in :func:`~mpmath.nsum` must not be chosen too large, otherwise + it will miss the point where the Levin transform converges resulting in numerical + overflow/garbage. For highly divergent series a copious amount of working precision + must be chosen. + + **Examples** + + First we sum the zeta function:: + + >>> from mpmath import mp + >>> mp.prec = 53 + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # levin needs a high working precision + ... L = mp.levin(method = "levin", variant = "u") + ... S, s, n = [], 0, 1 + ... while 1: + ... s += mp.one / (n * n) + ... n += 1 + ... S.append(s) + ... v, e = L.update_psum(S) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.pi ** 2 / 6)) + 0.0 + >>> w = mp.nsum(lambda n: 1 / (n*n), [1, mp.inf], method = "levin", levin_variant = "u") + >>> print(mp.chop(v - w)) + 0.0 + + Now we sum the zeta function outside its range of convergence + (attention: This does not work at the negative integers!):: + + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # levin needs a high working precision + ... L = mp.levin(method = "levin", variant = "v") + ... A, n = [], 1 + ... while 1: + ... s = mp.mpf(n) ** (2 + 3j) + ... n += 1 + ... A.append(s) + ... v, e = L.update(A) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.zeta(-2-3j))) + 0.0 + >>> w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + >>> print(mp.chop(v - w)) + 0.0 + + Now we sum the divergent asymptotic expansion of an integral related to the + exponential integral (see also [2] p.373). The Sidi-S transform works best here:: + + >>> z = mp.mpf(10) + >>> exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + >>> # exact = z * mp.exp(z) * mp.expint(1,z) # this is the symbolic expression for the integral + >>> eps = mp.mpf(mp.eps) + >>> with mp.extraprec(2 * mp.prec): # high working precisions are mandatory for divergent resummation + ... L = mp.levin(method = "sidi", variant = "t") + ... n = 0 + ... while 1: + ... s = (-1)**n * mp.fac(n) * z ** (-n) + ... v, e = L.step(s) + ... n += 1 + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - exact)) + 0.0 + >>> w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + >>> print(mp.chop(v - w)) + 0.0 + + Another highly divergent integral is also summable:: + + >>> z = mp.mpf(2) + >>> eps = mp.mpf(mp.eps) + >>> exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + >>> # exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) # this is the symbolic expression for the integral + >>> with mp.extraprec(7 * mp.prec): # we need copious amount of precision to sum this highly divergent series + ... L = mp.levin(method = "levin", variant = "t") + ... n, s = 0, 0 + ... while 1: + ... s += (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)) + ... n += 1 + ... v, e = L.step_psum(s) + ... if e < eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - exact)) + 0.0 + >>> w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), + ... [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in xrange(1000)]) + >>> print(mp.chop(v - w)) + 0.0 + + These examples run with 15-20 decimal digits precision. For higher precision the + working precision must be raised. + + **Examples for nsum** + + Here we calculate Euler's constant as the constant term in the Laurent + expansion of `\zeta(s)` at `s=1`. This sum converges extremly slowly because of + the logarithmic convergence behaviour of the Dirichlet series for zeta:: + + >>> mp.dps = 30 + >>> z = mp.mpf(10) ** (-10) + >>> a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "l") - 1 / z + >>> print(mp.chop(a - mp.euler, tol = 1e-10)) + 0.0 + + The Sidi-S transform performs excellently for the alternating series of `\log(2)`:: + + >>> a = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "sidi") + >>> print(mp.chop(a - mp.log(2))) + 0.0 + + Hypergeometric series can also be summed outside their range of convergence. + The stepsize in :func:`~mpmath.nsum` must not be chosen too large, otherwise it will miss the + point where the Levin transform converges resulting in numerical overflow/garbage:: + + >>> z = 2 + 1j + >>> exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + >>> f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + >>> v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in xrange(1000)]) + >>> print(mp.chop(exact-v)) + 0.0 + + References: + + [1] E.J. Weniger - "Nonlinear Sequence Transformations for the Acceleration of + Convergence and the Summation of Divergent Series" arXiv:math/0306302 + + [2] A. Sidi - "Pratical Extrapolation Methods" + + [3] H.H.H. Homeier - "Scalar Levin-Type Sequence Transformations" arXiv:math/0005209 + + """ + + def __init__(self, method = "levin", variant = "u"): + self.variant = variant + self.n = 0 + self.a0 = 0 + self.theta = 1 + self.A = [] + self.B = [] + self.last = 0 + self.last_s = False + + if method == "levin": + self.factor = self.factor_levin + elif method == "sidi": + self.factor = self.factor_sidi + else: + raise ValueError("levin: unknown method \"%s\"" % method) + + def factor_levin(self, i): + # original levin + # [1] p.50,e.7.5-7 (with n-j replaced by i) + return (self.theta + i) * (self.theta + self.n - 1) ** (self.n - i - 2) / self.ctx.mpf(self.theta + self.n) ** (self.n - i - 1) + + def factor_sidi(self, i): + # sidi analogon to levin (factorial series) + # [1] p.59,e.8.3-16 (with n-j replaced by i) + return (self.theta + self.n - 1) * (self.theta + self.n - 2) / self.ctx.mpf((self.theta + 2 * self.n - i - 2) * (self.theta + 2 * self.n - i - 3)) + + def run(self, s, a0, a1 = 0): + if self.variant=="t": + # levin t + w=a0 + elif self.variant=="u": + # levin u + w=a0*(self.theta+self.n) + elif self.variant=="v": + # levin v + w=a0*a1/(a0-a1) + else: + assert False, "unknown variant" + + if w==0: + raise ValueError("levin: zero weight") + + self.A.append(s/w) + self.B.append(1/w) + + for i in range(self.n-1,-1,-1): + if i==self.n-1: + f=1 + else: + f=self.factor(i) + + self.A[i]=self.A[i+1]-f*self.A[i] + self.B[i]=self.B[i+1]-f*self.B[i] + + self.n+=1 + + ########################################################################### + + def update_psum(self,S): + """ + This routine applies the convergence acceleration to the list of partial sums. + + A = sum(a_k, k = 0..infinity) + s_n = sum(a_k, k = 0..n) + + v, e = ...update_psum([s_0, s_1,..., s_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + if self.variant!="v": + if self.n==0: + self.run(S[0],S[0]) + while self.n>> from mpmath import mp + >>> AC = mp.cohen_alt() + >>> S, s, n = [], 0, 1 + >>> while 1: + ... s += -((-1) ** n) * mp.one / (n * n) + ... n += 1 + ... S.append(s) + ... v, e = AC.update_psum(S) + ... if e < mp.eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> print(mp.chop(v - mp.pi ** 2 / 12)) + 0.0 + + Here we compute the product `\prod_{n=1}^{\infty} \Gamma(1+1/(2n-1)) / \Gamma(1+1/(2n))`:: + + >>> A = [] + >>> AC = mp.cohen_alt() + >>> n = 1 + >>> while 1: + ... A.append( mp.loggamma(1 + mp.one / (2 * n - 1))) + ... A.append(-mp.loggamma(1 + mp.one / (2 * n))) + ... n += 1 + ... v, e = AC.update(A) + ... if e < mp.eps: + ... break + ... if n > 1000: raise RuntimeError("iteration limit exceeded") + >>> v = mp.exp(v) + >>> print(mp.chop(v - 1.06215090557106, tol = 1e-12)) + 0.0 + + ``cohen_alt`` is also accessible through the :func:`~mpmath.nsum` interface:: + + >>> v = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.log(2))) + 0.0 + >>> v = mp.nsum(lambda n: (-1)**n / (2 * n + 1), [0, mp.inf], method = "a") + >>> print(mp.chop(v - mp.pi / 4)) + 0.0 + >>> v = mp.nsum(lambda n: (-1)**n * mp.log(n) * n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.diff(lambda s: mp.altzeta(s), -1))) + 0.0 + + """ + + def __init__(self): + self.last=0 + + def update(self, A): + """ + This routine applies the convergence acceleration to the list of individual terms. + + A = sum(a_k, k = 0..infinity) + + v, e = ...update([a_0, a_1,..., a_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + n = len(A) + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = -self.ctx.one + c = -d + s = 0 + + for k in xrange(n): + c = b - c + if k % 2 == 0: + s = s + c * A[k] + else: + s = s - c * A[k] + b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one)) + + value = s / d + + err = abs(value - self.last) + self.last = value + + return value, err + + def update_psum(self, S): + """ + This routine applies the convergence acceleration to the list of partial sums. + + A = sum(a_k, k = 0..infinity) + s_n = sum(a_k ,k = 0..n) + + v, e = ...update_psum([s_0, s_1,..., s_k]) + + output: + v current estimate of the series A + e an error estimate which is simply the difference between the current + estimate and the last estimate. + """ + + n = len(S) + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = self.ctx.one + s = 0 + + for k in xrange(n): + b = 2 * (n + k) * (n - k) * b / ((2 * k + 1) * (k + self.ctx.one)) + s += b * S[k] + + value = s / d + + err = abs(value - self.last) + self.last = value + + return value, err + +def cohen_alt(ctx): + L = cohen_alt_class() + L.ctx = ctx + return L + +cohen_alt.__doc__ = cohen_alt_class.__doc__ +defun(cohen_alt) + + +@defun +def sumap(ctx, f, interval, integral=None, error=False): + r""" + Evaluates an infinite series of an analytic summand *f* using the + Abel-Plana formula + + .. math :: + + \sum_{k=0}^{\infty} f(k) = \int_0^{\infty} f(t) dt + \frac{1}{2} f(0) + + i \int_0^{\infty} \frac{f(it)-f(-it)}{e^{2\pi t}-1} dt. + + Unlike the Euler-Maclaurin formula (see :func:`~mpmath.sumem`), + the Abel-Plana formula does not require derivatives. However, + it only works when `|f(it)-f(-it)|` does not + increase too rapidly with `t`. + + **Examples** + + The Abel-Plana formula is particularly useful when the summand + decreases like a power of `k`; for example when the sum is a pure + zeta function:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> sumap(lambda k: 1/k**2.5, [1,inf]) + 1.34148725725091717975677 + >>> zeta(2.5) + 1.34148725725091717975677 + >>> sumap(lambda k: 1/(k+1j)**(2.5+2.5j), [1,inf]) + (-3.385361068546473342286084 - 0.7432082105196321803869551j) + >>> zeta(2.5+2.5j, 1+1j) + (-3.385361068546473342286084 - 0.7432082105196321803869551j) + + If the series is alternating, numerical quadrature along the real + line is likely to give poor results, so it is better to evaluate + the first term symbolically whenever possible: + + >>> n=3; z=-0.75 + >>> I = expint(n,-log(z)) + >>> chop(sumap(lambda k: z**k / k**n, [1,inf], integral=I)) + -0.6917036036904594510141448 + >>> polylog(n,z) + -0.6917036036904594510141448 + + """ + prec = ctx.prec + try: + ctx.prec += 10 + a, b = interval + if b != ctx.inf: + raise ValueError("b should be equal to ctx.inf") + g = lambda x: f(x+a) + if integral is None: + i1, err1 = ctx.quad(g, [0,ctx.inf], error=True) + else: + i1, err1 = integral, 0 + j = ctx.j + p = ctx.pi * 2 + if ctx._is_real_type(i1): + h = lambda t: -2 * ctx.im(g(j*t)) / ctx.expm1(p*t) + else: + h = lambda t: j*(g(j*t)-g(-j*t)) / ctx.expm1(p*t) + i2, err2 = ctx.quad(h, [0,ctx.inf], error=True) + err = err1+err2 + v = i1+i2+0.5*g(ctx.mpf(0)) + finally: + ctx.prec = prec + if error: + return +v, err + return +v + + +@defun +def sumem(ctx, f, interval, tol=None, reject=10, integral=None, + adiffs=None, bdiffs=None, verbose=False, error=False, + _fast_abort=False): + r""" + Uses the Euler-Maclaurin formula to compute an approximation accurate + to within ``tol`` (which defaults to the present epsilon) of the sum + + .. math :: + + S = \sum_{k=a}^b f(k) + + where `(a,b)` are given by ``interval`` and `a` or `b` may be + infinite. The approximation is + + .. math :: + + S \sim \int_a^b f(x) \,dx + \frac{f(a)+f(b)}{2} + + \sum_{k=1}^{\infty} \frac{B_{2k}}{(2k)!} + \left(f^{(2k-1)}(b)-f^{(2k-1)}(a)\right). + + The last sum in the Euler-Maclaurin formula is not generally + convergent (a notable exception is if `f` is a polynomial, in + which case Euler-Maclaurin actually gives an exact result). + + The summation is stopped as soon as the quotient between two + consecutive terms falls below *reject*. That is, by default + (*reject* = 10), the summation is continued as long as each + term adds at least one decimal. + + Although not convergent, convergence to a given tolerance can + often be "forced" if `b = \infty` by summing up to `a+N` and then + applying the Euler-Maclaurin formula to the sum over the range + `(a+N+1, \ldots, \infty)`. This procedure is implemented by + :func:`~mpmath.nsum`. + + By default numerical quadrature and differentiation is used. + If the symbolic values of the integral and endpoint derivatives + are known, it is more efficient to pass the value of the + integral explicitly as ``integral`` and the derivatives + explicitly as ``adiffs`` and ``bdiffs``. The derivatives + should be given as iterables that yield + `f(a), f'(a), f''(a), \ldots` (and the equivalent for `b`). + + **Examples** + + Summation of an infinite series, with automatic and symbolic + integral and derivative values (the second should be much faster):: + + >>> from mpmath import * + >>> mp.dps = 50; mp.pretty = True + >>> sumem(lambda n: 1/n**2, [32, inf]) + 0.03174336652030209012658168043874142714132886413417 + >>> I = mpf(1)/32 + >>> D = adiffs=((-1)**n*fac(n+1)*32**(-2-n) for n in range(999)) + >>> sumem(lambda n: 1/n**2, [32, inf], integral=I, adiffs=D) + 0.03174336652030209012658168043874142714132886413417 + + An exact evaluation of a finite polynomial sum:: + + >>> sumem(lambda n: n**5-12*n**2+3*n, [-100000, 200000]) + 10500155000624963999742499550000.0 + >>> print(sum(n**5-12*n**2+3*n for n in range(-100000, 200001))) + 10500155000624963999742499550000 + + """ + tol = tol or +ctx.eps + interval = ctx._as_points(interval) + a = ctx.convert(interval[0]) + b = ctx.convert(interval[-1]) + err = ctx.zero + prev = 0 + M = 10000 + if a == ctx.ninf: adiffs = (0 for n in xrange(M)) + else: adiffs = adiffs or ctx.diffs(f, a) + if b == ctx.inf: bdiffs = (0 for n in xrange(M)) + else: bdiffs = bdiffs or ctx.diffs(f, b) + orig = ctx.prec + #verbose = 1 + try: + ctx.prec += 10 + s = ctx.zero + for k, (da, db) in enumerate(izip(adiffs, bdiffs)): + if k & 1: + term = (db-da) * ctx.bernoulli(k+1) / ctx.factorial(k+1) + mag = abs(term) + if verbose: + print("term", k, "magnitude =", ctx.nstr(mag)) + if k > 4 and mag < tol: + s += term + break + elif k > 4 and abs(prev) / mag < reject: + err += mag + if _fast_abort: + return [s, (s, err)][error] + if verbose: + print("Failed to converge") + break + else: + s += term + prev = term + # Endpoint correction + if a != ctx.ninf: s += f(a)/2 + if b != ctx.inf: s += f(b)/2 + # Tail integral + if verbose: + print("Integrating f(x) from x = %s to %s" % (ctx.nstr(a), ctx.nstr(b))) + if integral: + s += integral + else: + integral, ierr = ctx.quad(f, interval, error=True) + if verbose: + print("Integration error:", ierr) + s += integral + err += ierr + finally: + ctx.prec = orig + if error: + return s, err + else: + return s + +@defun +def adaptive_extrapolation(ctx, update, emfun, kwargs): + option = kwargs.get + if ctx._fixed_precision: + tol = option('tol', ctx.eps*2**10) + else: + tol = option('tol', ctx.eps/2**10) + verbose = option('verbose', False) + maxterms = option('maxterms', ctx.dps*10) + method = set(option('method', 'r+s').split('+')) + skip = option('skip', 0) + steps = iter(option('steps', xrange(10, 10**9, 10))) + strict = option('strict') + #steps = (10 for i in xrange(1000)) + summer=[] + if 'd' in method or 'direct' in method: + TRY_RICHARDSON = TRY_SHANKS = TRY_EULER_MACLAURIN = False + else: + TRY_RICHARDSON = ('r' in method) or ('richardson' in method) + TRY_SHANKS = ('s' in method) or ('shanks' in method) + TRY_EULER_MACLAURIN = ('e' in method) or \ + ('euler-maclaurin' in method) + + def init_levin(m): + variant = kwargs.get("levin_variant", "u") + if isinstance(variant, str): + if variant == "all": + variant = ["u", "v", "t"] + else: + variant = [variant] + for s in variant: + L = levin_class(method = m, variant = s) + L.ctx = ctx + L.name = m + "(" + s + ")" + summer.append(L) + + if ('l' in method) or ('levin' in method): + init_levin("levin") + + if ('sidi' in method): + init_levin("sidi") + + if ('a' in method) or ('alternating' in method): + L = cohen_alt_class() + L.ctx = ctx + L.name = "alternating" + summer.append(L) + + last_richardson_value = 0 + shanks_table = [] + index = 0 + step = 10 + partial = [] + best = ctx.zero + orig = ctx.prec + try: + if 'workprec' in kwargs: + ctx.prec = kwargs['workprec'] + elif TRY_RICHARDSON or TRY_SHANKS or len(summer)!=0: + ctx.prec = (ctx.prec+10) * 4 + else: + ctx.prec += 30 + while 1: + if index >= maxterms: + break + + # Get new batch of terms + try: + step = next(steps) + except StopIteration: + pass + if verbose: + print("-"*70) + print("Adding terms #%i-#%i" % (index, index+step)) + update(partial, xrange(index, index+step)) + index += step + + # Check direct error + best = partial[-1] + error = abs(best - partial[-2]) + if verbose: + print("Direct error: %s" % ctx.nstr(error)) + if error <= tol: + return best + + # Check each extrapolation method + if TRY_RICHARDSON: + value, maxc = ctx.richardson(partial) + # Convergence + richardson_error = abs(value - last_richardson_value) + if verbose: + print("Richardson error: %s" % ctx.nstr(richardson_error)) + # Convergence + if richardson_error <= tol: + return value + last_richardson_value = value + # Unreliable due to cancellation + if ctx.eps*maxc > tol: + if verbose: + print("Ran out of precision for Richardson") + TRY_RICHARDSON = False + if richardson_error < error: + error = richardson_error + best = value + if TRY_SHANKS: + shanks_table = ctx.shanks(partial, shanks_table, randomized=True) + row = shanks_table[-1] + if len(row) == 2: + est1 = row[-1] + shanks_error = 0 + else: + est1, maxc, est2 = row[-1], abs(row[-2]), row[-3] + shanks_error = abs(est1-est2) + if verbose: + print("Shanks error: %s" % ctx.nstr(shanks_error)) + if shanks_error <= tol: + return est1 + if ctx.eps*maxc > tol: + if verbose: + print("Ran out of precision for Shanks") + TRY_SHANKS = False + if shanks_error < error: + error = shanks_error + best = est1 + for L in summer: + est, lerror = L.update_psum(partial) + if verbose: + print("%s error: %s" % (L.name, ctx.nstr(lerror))) + if lerror <= tol: + return est + if lerror < error: + error = lerror + best = est + if TRY_EULER_MACLAURIN: + if ctx.almosteq(ctx.mpc(ctx.sign(partial[-1]) / ctx.sign(partial[-2])), -1): + if verbose: + print ("NOT using Euler-Maclaurin: the series appears" + " to be alternating, so numerical\n quadrature" + " will most likely fail") + TRY_EULER_MACLAURIN = False + else: + value, em_error = emfun(index, tol) + value += partial[-1] + if verbose: + print("Euler-Maclaurin error: %s" % ctx.nstr(em_error)) + if em_error <= tol: + return value + if em_error < error: + best = value + finally: + ctx.prec = orig + if strict: + raise ctx.NoConvergence + if verbose: + print("Warning: failed to converge to target accuracy") + return best + +@defun +def nsum(ctx, f, *intervals, **options): + r""" + Computes the sum + + .. math :: S = \sum_{k=a}^b f(k) + + where `(a, b)` = *interval*, and where `a = -\infty` and/or + `b = \infty` are allowed, or more generally + + .. math :: S = \sum_{k_1=a_1}^{b_1} \cdots + \sum_{k_n=a_n}^{b_n} f(k_1,\ldots,k_n) + + if multiple intervals are given. + + Two examples of infinite series that can be summed by :func:`~mpmath.nsum`, + where the first converges rapidly and the second converges slowly, + are:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> nsum(lambda n: 1/fac(n), [0, inf]) + 2.71828182845905 + >>> nsum(lambda n: 1/n**2, [1, inf]) + 1.64493406684823 + + When appropriate, :func:`~mpmath.nsum` applies convergence acceleration to + accurately estimate the sums of slowly convergent series. If the series is + finite, :func:`~mpmath.nsum` currently does not attempt to perform any + extrapolation, and simply calls :func:`~mpmath.fsum`. + + Multidimensional infinite series are reduced to a single-dimensional + series over expanding hypercubes; if both infinite and finite dimensions + are present, the finite ranges are moved innermost. For more advanced + control over the summation order, use nested calls to :func:`~mpmath.nsum`, + or manually rewrite the sum as a single-dimensional series. + + **Options** + + *tol* + Desired maximum final error. Defaults roughly to the + epsilon of the working precision. + + *method* + Which summation algorithm to use (described below). + Default: ``'richardson+shanks'``. + + *maxterms* + Cancel after at most this many terms. Default: 10*dps. + + *steps* + An iterable giving the number of terms to add between + each extrapolation attempt. The default sequence is + [10, 20, 30, 40, ...]. For example, if you know that + approximately 100 terms will be required, efficiency might be + improved by setting this to [100, 10]. Then the first + extrapolation will be performed after 100 terms, the second + after 110, etc. + + *verbose* + Print details about progress. + + *ignore* + If enabled, any term that raises ``ArithmeticError`` + or ``ValueError`` (e.g. through division by zero) is replaced + by a zero. This is convenient for lattice sums with + a singular term near the origin. + + **Methods** + + Unfortunately, an algorithm that can efficiently sum any infinite + series does not exist. :func:`~mpmath.nsum` implements several different + algorithms that each work well in different cases. The *method* + keyword argument selects a method. + + The default method is ``'r+s'``, i.e. both Richardson extrapolation + and Shanks transformation is attempted. A slower method that + handles more cases is ``'r+s+e'``. For very high precision + summation, or if the summation needs to be fast (for example if + multiple sums need to be evaluated), it is a good idea to + investigate which one method works best and only use that. + + ``'richardson'`` / ``'r'``: + Uses Richardson extrapolation. Provides useful extrapolation + when `f(k) \sim P(k)/Q(k)` or when `f(k) \sim (-1)^k P(k)/Q(k)` + for polynomials `P` and `Q`. See :func:`~mpmath.richardson` for + additional information. + + ``'shanks'`` / ``'s'``: + Uses Shanks transformation. Typically provides useful + extrapolation when `f(k) \sim c^k` or when successive terms + alternate signs. Is able to sum some divergent series. + See :func:`~mpmath.shanks` for additional information. + + ``'levin'`` / ``'l'``: + Uses the Levin transformation. It performs better than the Shanks + transformation for logarithmic convergent or alternating divergent + series. The ``'levin_variant'``-keyword selects the variant. Valid + choices are "u", "t", "v" and "all" whereby "all" uses all three + u,t and v simultanously (This is good for performance comparison in + conjunction with "verbose=True"). Instead of the Levin transform one can + also use the Sidi-S transform by selecting the method ``'sidi'``. + See :func:`~mpmath.levin` for additional details. + + ``'alternating'`` / ``'a'``: + This is the convergence acceleration of alternating series developped + by Cohen, Villegras and Zagier. + See :func:`~mpmath.cohen_alt` for additional details. + + ``'euler-maclaurin'`` / ``'e'``: + Uses the Euler-Maclaurin summation formula to approximate + the remainder sum by an integral. This requires high-order + numerical derivatives and numerical integration. The advantage + of this algorithm is that it works regardless of the + decay rate of `f`, as long as `f` is sufficiently smooth. + See :func:`~mpmath.sumem` for additional information. + + ``'direct'`` / ``'d'``: + Does not perform any extrapolation. This can be used + (and should only be used for) rapidly convergent series. + The summation automatically stops when the terms + decrease below the target tolerance. + + **Basic examples** + + A finite sum:: + + >>> nsum(lambda k: 1/k, [1, 6]) + 2.45 + + Summation of a series going to negative infinity and a doubly + infinite series:: + + >>> nsum(lambda k: 1/k**2, [-inf, -1]) + 1.64493406684823 + >>> nsum(lambda k: 1/(1+k**2), [-inf, inf]) + 3.15334809493716 + + :func:`~mpmath.nsum` handles sums of complex numbers:: + + >>> nsum(lambda k: (0.5+0.25j)**k, [0, inf]) + (1.6 + 0.8j) + + The following sum converges very rapidly, so it is most + efficient to sum it by disabling convergence acceleration:: + + >>> mp.dps = 1000 + >>> a = nsum(lambda k: -(-1)**k * k**2 / fac(2*k), [1, inf], + ... method='direct') + >>> b = (cos(1)+sin(1))/4 + >>> abs(a-b) < mpf('1e-998') + True + + **Examples with Richardson extrapolation** + + Richardson extrapolation works well for sums over rational + functions, as well as their alternating counterparts:: + + >>> mp.dps = 50 + >>> nsum(lambda k: 1 / k**3, [1, inf], + ... method='richardson') + 1.2020569031595942853997381615114499907649862923405 + >>> zeta(3) + 1.2020569031595942853997381615114499907649862923405 + + >>> nsum(lambda n: (n + 3)/(n**3 + n**2), [1, inf], + ... method='richardson') + 2.9348022005446793094172454999380755676568497036204 + >>> pi**2/2-2 + 2.9348022005446793094172454999380755676568497036204 + + >>> nsum(lambda k: (-1)**k / k**3, [1, inf], + ... method='richardson') + -0.90154267736969571404980362113358749307373971925537 + >>> -3*zeta(3)/4 + -0.90154267736969571404980362113358749307373971925538 + + **Examples with Shanks transformation** + + The Shanks transformation works well for geometric series + and typically provides excellent acceleration for Taylor + series near the border of their disk of convergence. + Here we apply it to a series for `\log(2)`, which can be + seen as the Taylor series for `\log(1+x)` with `x = 1`:: + + >>> nsum(lambda k: -(-1)**k/k, [1, inf], + ... method='shanks') + 0.69314718055994530941723212145817656807550013436025 + >>> log(2) + 0.69314718055994530941723212145817656807550013436025 + + Here we apply it to a slowly convergent geometric series:: + + >>> nsum(lambda k: mpf('0.995')**k, [0, inf], + ... method='shanks') + 200.0 + + Finally, Shanks' method works very well for alternating series + where `f(k) = (-1)^k g(k)`, and often does so regardless of + the exact decay rate of `g(k)`:: + + >>> mp.dps = 15 + >>> nsum(lambda k: (-1)**(k+1) / k**1.5, [1, inf], + ... method='shanks') + 0.765147024625408 + >>> (2-sqrt(2))*zeta(1.5)/2 + 0.765147024625408 + + The following slowly convergent alternating series has no known + closed-form value. Evaluating the sum a second time at higher + precision indicates that the value is probably correct:: + + >>> nsum(lambda k: (-1)**k / log(k), [2, inf], + ... method='shanks') + 0.924299897222939 + >>> mp.dps = 30 + >>> nsum(lambda k: (-1)**k / log(k), [2, inf], + ... method='shanks') + 0.92429989722293885595957018136 + + **Examples with Levin transformation** + + The following example calculates Euler's constant as the constant term in + the Laurent expansion of zeta(s) at s=1. This sum converges extremly slow + because of the logarithmic convergence behaviour of the Dirichlet series + for zeta. + + >>> mp.dps = 30 + >>> z = mp.mpf(10) ** (-10) + >>> a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "levin") - 1 / z + >>> print(mp.chop(a - mp.euler, tol = 1e-10)) + 0.0 + + Now we sum the zeta function outside its range of convergence + (attention: This does not work at the negative integers!): + + >>> mp.dps = 15 + >>> w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + >>> print(mp.chop(w - mp.zeta(-2-3j))) + 0.0 + + The next example resummates an asymptotic series expansion of an integral + related to the exponential integral. + + >>> mp.dps = 15 + >>> z = mp.mpf(10) + >>> # exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + >>> exact = z * mp.exp(z) * mp.expint(1,z) # this is the symbolic expression for the integral + >>> w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + >>> print(mp.chop(w - exact)) + 0.0 + + Following highly divergent asymptotic expansion needs some care. Firstly we + need copious amount of working precision. Secondly the stepsize must not be + chosen to large, otherwise nsum may miss the point where the Levin transform + converges and reach the point where only numerical garbage is produced due to + numerical cancellation. + + >>> mp.dps = 15 + >>> z = mp.mpf(2) + >>> # exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + >>> exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) # this is the symbolic expression for the integral + >>> w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), + ... [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in xrange(1000)]) + >>> print(mp.chop(w - exact)) + 0.0 + + The hypergeoemtric function can also be summed outside its range of convergence: + + >>> mp.dps = 15 + >>> z = 2 + 1j + >>> exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + >>> f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + >>> v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in xrange(1000)]) + >>> print(mp.chop(exact-v)) + 0.0 + + **Examples with Cohen's alternating series resummation** + + The next example sums the alternating zeta function: + + >>> v = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.log(2))) + 0.0 + + The derivate of the alternating zeta function outside its range of + convergence: + + >>> v = mp.nsum(lambda n: (-1)**n * mp.log(n) * n, [1, mp.inf], method = "a") + >>> print(mp.chop(v - mp.diff(lambda s: mp.altzeta(s), -1))) + 0.0 + + **Examples with Euler-Maclaurin summation** + + The sum in the following example has the wrong rate of convergence + for either Richardson or Shanks to be effective. + + >>> f = lambda k: log(k)/k**2.5 + >>> mp.dps = 15 + >>> nsum(f, [1, inf], method='euler-maclaurin') + 0.38734195032621 + >>> -diff(zeta, 2.5) + 0.38734195032621 + + Increasing ``steps`` improves speed at higher precision:: + + >>> mp.dps = 50 + >>> nsum(f, [1, inf], method='euler-maclaurin', steps=[250]) + 0.38734195032620997271199237593105101319948228874688 + >>> -diff(zeta, 2.5) + 0.38734195032620997271199237593105101319948228874688 + + **Divergent series** + + The Shanks transformation is able to sum some *divergent* + series. In particular, it is often able to sum Taylor series + beyond their radius of convergence (this is due to a relation + between the Shanks transformation and Pade approximations; + see :func:`~mpmath.pade` for an alternative way to evaluate divergent + Taylor series). Furthermore the Levin-transform examples above + contain some divergent series resummation. + + Here we apply it to `\log(1+x)` far outside the region of + convergence:: + + >>> mp.dps = 50 + >>> nsum(lambda k: -(-9)**k/k, [1, inf], + ... method='shanks') + 2.3025850929940456840179914546843642076011014886288 + >>> log(10) + 2.3025850929940456840179914546843642076011014886288 + + A particular type of divergent series that can be summed + using the Shanks transformation is geometric series. + The result is the same as using the closed-form formula + for an infinite geometric series:: + + >>> mp.dps = 15 + >>> for n in range(-8, 8): + ... if n == 1: + ... continue + ... print("%s %s %s" % (mpf(n), mpf(1)/(1-n), + ... nsum(lambda k: n**k, [0, inf], method='shanks'))) + ... + -8.0 0.111111111111111 0.111111111111111 + -7.0 0.125 0.125 + -6.0 0.142857142857143 0.142857142857143 + -5.0 0.166666666666667 0.166666666666667 + -4.0 0.2 0.2 + -3.0 0.25 0.25 + -2.0 0.333333333333333 0.333333333333333 + -1.0 0.5 0.5 + 0.0 1.0 1.0 + 2.0 -1.0 -1.0 + 3.0 -0.5 -0.5 + 4.0 -0.333333333333333 -0.333333333333333 + 5.0 -0.25 -0.25 + 6.0 -0.2 -0.2 + 7.0 -0.166666666666667 -0.166666666666667 + + **Multidimensional sums** + + Any combination of finite and infinite ranges is allowed for the + summation indices:: + + >>> mp.dps = 15 + >>> nsum(lambda x,y: x+y, [2,3], [4,5]) + 28.0 + >>> nsum(lambda x,y: x/2**y, [1,3], [1,inf]) + 6.0 + >>> nsum(lambda x,y: y/2**x, [1,inf], [1,3]) + 6.0 + >>> nsum(lambda x,y,z: z/(2**x*2**y), [1,inf], [1,inf], [3,4]) + 7.0 + >>> nsum(lambda x,y,z: y/(2**x*2**z), [1,inf], [3,4], [1,inf]) + 7.0 + >>> nsum(lambda x,y,z: x/(2**z*2**y), [3,4], [1,inf], [1,inf]) + 7.0 + + Some nice examples of double series with analytic solutions or + reductions to single-dimensional series (see [1]):: + + >>> nsum(lambda m, n: 1/2**(m*n), [1,inf], [1,inf]) + 1.60669515241529 + >>> nsum(lambda n: 1/(2**n-1), [1,inf]) + 1.60669515241529 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i**2+j**2), [1,inf], [1,inf]) + 0.278070510848213 + >>> pi*(pi-3*ln2)/12 + 0.278070510848213 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i+j)**2, [1,inf], [1,inf]) + 0.129319852864168 + >>> altzeta(2) - altzeta(1) + 0.129319852864168 + + >>> nsum(lambda i,j: (-1)**(i+j)/(i+j)**3, [1,inf], [1,inf]) + 0.0790756439455825 + >>> altzeta(3) - altzeta(2) + 0.0790756439455825 + + >>> nsum(lambda m,n: m**2*n/(3**m*(n*3**m+m*3**n)), + ... [1,inf], [1,inf]) + 0.28125 + >>> mpf(9)/32 + 0.28125 + + >>> nsum(lambda i,j: fac(i-1)*fac(j-1)/fac(i+j), + ... [1,inf], [1,inf], workprec=400) + 1.64493406684823 + >>> zeta(2) + 1.64493406684823 + + A hard example of a multidimensional sum is the Madelung constant + in three dimensions (see [2]). The defining sum converges very + slowly and only conditionally, so :func:`~mpmath.nsum` is lucky to + obtain an accurate value through convergence acceleration. The + second evaluation below uses a much more efficient, rapidly + convergent 2D sum:: + + >>> nsum(lambda x,y,z: (-1)**(x+y+z)/(x*x+y*y+z*z)**0.5, + ... [-inf,inf], [-inf,inf], [-inf,inf], ignore=True) + -1.74756459463318 + >>> nsum(lambda x,y: -12*pi*sech(0.5*pi * \ + ... sqrt((2*x+1)**2+(2*y+1)**2))**2, [0,inf], [0,inf]) + -1.74756459463318 + + Another example of a lattice sum in 2D:: + + >>> nsum(lambda x,y: (-1)**(x+y) / (x**2+y**2), [-inf,inf], + ... [-inf,inf], ignore=True) + -2.1775860903036 + >>> -pi*ln2 + -2.1775860903036 + + An example of an Eisenstein series:: + + >>> nsum(lambda m,n: (m+n*1j)**(-4), [-inf,inf], [-inf,inf], + ... ignore=True) + (3.1512120021539 + 0.0j) + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/DoubleSeries.html, + 2. [Weisstein]_ http://mathworld.wolfram.com/MadelungConstants.html + + """ + infinite, g = standardize(ctx, f, intervals, options) + if not infinite: + return +g() + + def update(partial_sums, indices): + if partial_sums: + psum = partial_sums[-1] + else: + psum = ctx.zero + for k in indices: + psum = psum + g(ctx.mpf(k)) + partial_sums.append(psum) + + prec = ctx.prec + + def emfun(point, tol): + workprec = ctx.prec + ctx.prec = prec + 10 + v = ctx.sumem(g, [point, ctx.inf], tol, error=1) + ctx.prec = workprec + return v + + return +ctx.adaptive_extrapolation(update, emfun, options) + + +def wrapsafe(f): + def g(*args): + try: + return f(*args) + except (ArithmeticError, ValueError): + return 0 + return g + +def standardize(ctx, f, intervals, options): + if options.get("ignore"): + f = wrapsafe(f) + finite = [] + infinite = [] + for k, points in enumerate(intervals): + a, b = ctx._as_points(points) + if b < a: + return False, (lambda: ctx.zero) + if a == ctx.ninf or b == ctx.inf: + infinite.append((k, (a,b))) + else: + finite.append((k, (int(a), int(b)))) + if finite: + f = fold_finite(ctx, f, finite) + if not infinite: + return False, lambda: f(*([0]*len(intervals))) + if infinite: + f = standardize_infinite(ctx, f, infinite) + f = fold_infinite(ctx, f, infinite) + args = [0] * len(intervals) + d = infinite[0][0] + def g(k): + args[d] = k + return f(*args) + return True, g + +# backwards compatible itertools.product +def cartesian_product(args): + pools = map(tuple, args) + result = [[]] + for pool in pools: + result = [x+[y] for x in result for y in pool] + for prod in result: + yield tuple(prod) + +def fold_finite(ctx, f, intervals): + if not intervals: + return f + indices = [v[0] for v in intervals] + points = [v[1] for v in intervals] + ranges = [xrange(a, b+1) for (a,b) in points] + def g(*args): + args = list(args) + s = ctx.zero + for xs in cartesian_product(ranges): + for dim, x in zip(indices, xs): + args[dim] = ctx.mpf(x) + s += f(*args) + return s + #print "Folded finite", indices + return g + +# Standardize each interval to [0,inf] +def standardize_infinite(ctx, f, intervals): + if not intervals: + return f + dim, [a,b] = intervals[-1] + if a == ctx.ninf: + if b == ctx.inf: + def g(*args): + args = list(args) + k = args[dim] + if k: + s = f(*args) + args[dim] = -k + s += f(*args) + return s + else: + return f(*args) + else: + def g(*args): + args = list(args) + args[dim] = b - args[dim] + return f(*args) + else: + def g(*args): + args = list(args) + args[dim] += a + return f(*args) + #print "Standardized infinity along dimension", dim, a, b + return standardize_infinite(ctx, g, intervals[:-1]) + +def fold_infinite(ctx, f, intervals): + if len(intervals) < 2: + return f + dim1 = intervals[-2][0] + dim2 = intervals[-1][0] + # Assume intervals are [0,inf] x [0,inf] x ... + def g(*args): + args = list(args) + #args.insert(dim2, None) + n = int(args[dim1]) + s = ctx.zero + #y = ctx.mpf(n) + args[dim2] = ctx.mpf(n) #y + for x in xrange(n+1): + args[dim1] = ctx.mpf(x) + s += f(*args) + args[dim1] = ctx.mpf(n) #ctx.mpf(n) + for y in xrange(n): + args[dim2] = ctx.mpf(y) + s += f(*args) + return s + #print "Folded infinite from", len(intervals), "to", (len(intervals)-1) + return fold_infinite(ctx, g, intervals[:-1]) + +@defun +def nprod(ctx, f, interval, nsum=False, **kwargs): + r""" + Computes the product + + .. math :: + + P = \prod_{k=a}^b f(k) + + where `(a, b)` = *interval*, and where `a = -\infty` and/or + `b = \infty` are allowed. + + By default, :func:`~mpmath.nprod` uses the same extrapolation methods as + :func:`~mpmath.nsum`, except applied to the partial products rather than + partial sums, and the same keyword options as for :func:`~mpmath.nsum` are + supported. If ``nsum=True``, the product is instead computed via + :func:`~mpmath.nsum` as + + .. math :: + + P = \exp\left( \sum_{k=a}^b \log(f(k)) \right). + + This is slower, but can sometimes yield better results. It is + also required (and used automatically) when Euler-Maclaurin + summation is requested. + + **Examples** + + A simple finite product:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> nprod(lambda k: k, [1, 4]) + 24.0 + + A large number of infinite products have known exact values, + and can therefore be used as a reference. Most of the following + examples are taken from MathWorld [1]. + + A few infinite products with simple values are:: + + >>> 2*nprod(lambda k: (4*k**2)/(4*k**2-1), [1, inf]) + 3.141592653589793238462643 + >>> nprod(lambda k: (1+1/k)**2/(1+2/k), [1, inf]) + 2.0 + >>> nprod(lambda k: (k**3-1)/(k**3+1), [2, inf]) + 0.6666666666666666666666667 + >>> nprod(lambda k: (1-1/k**2), [2, inf]) + 0.5 + + Next, several more infinite products with more complicated + values:: + + >>> nprod(lambda k: exp(1/k**2), [1, inf]); exp(pi**2/6) + 5.180668317897115748416626 + 5.180668317897115748416626 + + >>> nprod(lambda k: (k**2-1)/(k**2+1), [2, inf]); pi*csch(pi) + 0.2720290549821331629502366 + 0.2720290549821331629502366 + + >>> nprod(lambda k: (k**4-1)/(k**4+1), [2, inf]) + 0.8480540493529003921296502 + >>> pi*sinh(pi)/(cosh(sqrt(2)*pi)-cos(sqrt(2)*pi)) + 0.8480540493529003921296502 + + >>> nprod(lambda k: (1+1/k+1/k**2)**2/(1+2/k+3/k**2), [1, inf]) + 1.848936182858244485224927 + >>> 3*sqrt(2)*cosh(pi*sqrt(3)/2)**2*csch(pi*sqrt(2))/pi + 1.848936182858244485224927 + + >>> nprod(lambda k: (1-1/k**4), [2, inf]); sinh(pi)/(4*pi) + 0.9190194775937444301739244 + 0.9190194775937444301739244 + + >>> nprod(lambda k: (1-1/k**6), [2, inf]) + 0.9826842777421925183244759 + >>> (1+cosh(pi*sqrt(3)))/(12*pi**2) + 0.9826842777421925183244759 + + >>> nprod(lambda k: (1+1/k**2), [2, inf]); sinh(pi)/(2*pi) + 1.838038955187488860347849 + 1.838038955187488860347849 + + >>> nprod(lambda n: (1+1/n)**n * exp(1/(2*n)-1), [1, inf]) + 1.447255926890365298959138 + >>> exp(1+euler/2)/sqrt(2*pi) + 1.447255926890365298959138 + + The following two products are equivalent and can be evaluated in + terms of a Jacobi theta function. Pi can be replaced by any value + (as long as convergence is preserved):: + + >>> nprod(lambda k: (1-pi**-k)/(1+pi**-k), [1, inf]) + 0.3838451207481672404778686 + >>> nprod(lambda k: tanh(k*log(pi)/2), [1, inf]) + 0.3838451207481672404778686 + >>> jtheta(4,0,1/pi) + 0.3838451207481672404778686 + + This product does not have a known closed form value:: + + >>> nprod(lambda k: (1-1/2**k), [1, inf]) + 0.2887880950866024212788997 + + A product taken from `-\infty`:: + + >>> nprod(lambda k: 1-k**(-3), [-inf,-2]) + 0.8093965973662901095786805 + >>> cosh(pi*sqrt(3)/2)/(3*pi) + 0.8093965973662901095786805 + + A doubly infinite product:: + + >>> nprod(lambda k: exp(1/(1+k**2)), [-inf, inf]) + 23.41432688231864337420035 + >>> exp(pi/tanh(pi)) + 23.41432688231864337420035 + + A product requiring the use of Euler-Maclaurin summation to compute + an accurate value:: + + >>> nprod(lambda k: (1-1/k**2.5), [2, inf], method='e') + 0.696155111336231052898125 + + **References** + + 1. [Weisstein]_ http://mathworld.wolfram.com/InfiniteProduct.html + + """ + if nsum or ('e' in kwargs.get('method', '')): + orig = ctx.prec + try: + # TODO: we are evaluating log(1+eps) -> eps, which is + # inaccurate. This currently works because nsum greatly + # increases the working precision. But we should be + # more intelligent and handle the precision here. + ctx.prec += 10 + v = ctx.nsum(lambda n: ctx.ln(f(n)), interval, **kwargs) + finally: + ctx.prec = orig + return +ctx.exp(v) + + a, b = ctx._as_points(interval) + if a == ctx.ninf: + if b == ctx.inf: + return f(0) * ctx.nprod(lambda k: f(-k) * f(k), [1, ctx.inf], **kwargs) + return ctx.nprod(f, [-b, ctx.inf], **kwargs) + elif b != ctx.inf: + return ctx.fprod(f(ctx.mpf(k)) for k in xrange(int(a), int(b)+1)) + + a = int(a) + + def update(partial_products, indices): + if partial_products: + pprod = partial_products[-1] + else: + pprod = ctx.one + for k in indices: + pprod = pprod * f(a + ctx.mpf(k)) + partial_products.append(pprod) + + return +ctx.adaptive_extrapolation(update, None, kwargs) + + +@defun +def limit(ctx, f, x, direction=1, exp=False, **kwargs): + r""" + Computes an estimate of the limit + + .. math :: + + \lim_{t \to x} f(t) + + where `x` may be finite or infinite. + + For finite `x`, :func:`~mpmath.limit` evaluates `f(x + d/n)` for + consecutive integer values of `n`, where the approach direction + `d` may be specified using the *direction* keyword argument. + For infinite `x`, :func:`~mpmath.limit` evaluates values of + `f(\mathrm{sign}(x) \cdot n)`. + + If the approach to the limit is not sufficiently fast to give + an accurate estimate directly, :func:`~mpmath.limit` attempts to find + the limit using Richardson extrapolation or the Shanks + transformation. You can select between these methods using + the *method* keyword (see documentation of :func:`~mpmath.nsum` for + more information). + + **Options** + + The following options are available with essentially the + same meaning as for :func:`~mpmath.nsum`: *tol*, *method*, *maxterms*, + *steps*, *verbose*. + + If the option *exp=True* is set, `f` will be + sampled at exponentially spaced points `n = 2^1, 2^2, 2^3, \ldots` + instead of the linearly spaced points `n = 1, 2, 3, \ldots`. + This can sometimes improve the rate of convergence so that + :func:`~mpmath.limit` may return a more accurate answer (and faster). + However, do note that this can only be used if `f` + supports fast and accurate evaluation for arguments that + are extremely close to the limit point (or if infinite, + very large arguments). + + **Examples** + + A basic evaluation of a removable singularity:: + + >>> from mpmath import * + >>> mp.dps = 30; mp.pretty = True + >>> limit(lambda x: (x-sin(x))/x**3, 0) + 0.166666666666666666666666666667 + + Computing the exponential function using its limit definition:: + + >>> limit(lambda n: (1+3/n)**n, inf) + 20.0855369231876677409285296546 + >>> exp(3) + 20.0855369231876677409285296546 + + A limit for `\pi`:: + + >>> f = lambda n: 2**(4*n+1)*fac(n)**4/(2*n+1)/fac(2*n)**2 + >>> limit(f, inf) + 3.14159265358979323846264338328 + + Calculating the coefficient in Stirling's formula:: + + >>> limit(lambda n: fac(n) / (sqrt(n)*(n/e)**n), inf) + 2.50662827463100050241576528481 + >>> sqrt(2*pi) + 2.50662827463100050241576528481 + + Evaluating Euler's constant `\gamma` using the limit representation + + .. math :: + + \gamma = \lim_{n \rightarrow \infty } \left[ \left( + \sum_{k=1}^n \frac{1}{k} \right) - \log(n) \right] + + (which converges notoriously slowly):: + + >>> f = lambda n: sum([mpf(1)/k for k in range(1,int(n)+1)]) - log(n) + >>> limit(f, inf) + 0.577215664901532860606512090082 + >>> +euler + 0.577215664901532860606512090082 + + With default settings, the following limit converges too slowly + to be evaluated accurately. Changing to exponential sampling + however gives a perfect result:: + + >>> f = lambda x: sqrt(x**3+x**2)/(sqrt(x**3)+x) + >>> limit(f, inf) + 0.992831158558330281129249686491 + >>> limit(f, inf, exp=True) + 1.0 + + """ + + if ctx.isinf(x): + direction = ctx.sign(x) + g = lambda k: f(ctx.mpf(k+1)*direction) + else: + direction *= ctx.one + g = lambda k: f(x + direction/(k+1)) + if exp: + h = g + g = lambda k: h(2**k) + + def update(values, indices): + for k in indices: + values.append(g(k+1)) + + # XXX: steps used by nsum don't work well + if not 'steps' in kwargs: + kwargs['steps'] = [10] + + return +ctx.adaptive_extrapolation(update, None, kwargs) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/inverselaplace.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/inverselaplace.py new file mode 100644 index 0000000000000000000000000000000000000000..d2206b05c1601ee781b09dcbedf3c0fcd89cfa59 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/inverselaplace.py @@ -0,0 +1,973 @@ +# contributed to mpmath by Kristopher L. Kuhlman, February 2017 +# contributed to mpmath by Guillermo Navas-Palencia, February 2022 + +class InverseLaplaceTransform(object): + r""" + Inverse Laplace transform methods are implemented using this + class, in order to simplify the code and provide a common + infrastructure. + + Implement a custom inverse Laplace transform algorithm by + subclassing :class:`InverseLaplaceTransform` and implementing the + appropriate methods. The subclass can then be used by + :func:`~mpmath.invertlaplace` by passing it as the *method* + argument. + """ + + def __init__(self, ctx): + self.ctx = ctx + + def calc_laplace_parameter(self, t, **kwargs): + r""" + Determine the vector of Laplace parameter values needed for an + algorithm, this will depend on the choice of algorithm (de + Hoog is default), the algorithm-specific parameters passed (or + default ones), and desired time. + """ + raise NotImplementedError + + def calc_time_domain_solution(self, fp): + r""" + Compute the time domain solution, after computing the + Laplace-space function evaluations at the abscissa required + for the algorithm. Abscissa computed for one algorithm are + typically not useful for another algorithm. + """ + raise NotImplementedError + + +class FixedTalbot(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""The "fixed" Talbot method deforms the Bromwich contour towards + `-\infty` in the shape of a parabola. Traditionally the Talbot + algorithm has adjustable parameters, but the "fixed" version + does not. The `r` parameter could be passed in as a parameter, + if you want to override the default given by (Abate & Valko, + 2004). + + The Laplace parameter is sampled along a parabola opening + along the negative imaginary axis, with the base of the + parabola along the real axis at + `p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in + the approximation (degree) grows, the abscissa required for + function evaluation tend towards `-\infty`, requiring high + precision to prevent overflow. If any poles, branch cuts or + other singularities exist such that the deformed Bromwich + contour lies to the left of the singularity, the method will + fail. + + **Optional arguments** + + :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` + recognizes the following keywords + + *tmax* + maximum time associated with vector of times + (typically just the time requested) + *degree* + integer order of approximation (M = number of terms) + *r* + abscissa for `p_0` (otherwise computed using rule + of thumb `2M/5`) + + The working precision will be increased according to a rule of + thumb. If 'degree' is not specified, the working precision and + degree are chosen to hopefully achieve the dps of the calling + context. If 'degree' is specified, the working precision is + chosen to achieve maximum resulting precision for the + specified degree. + + .. math :: + + p_0=\frac{r}{t} + + .. math :: + + p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left( + \frac{i\pi}{M}\right) + j \right] \qquad 1\le i 0: + self.degree += 1 + + M = self.degree + + # this is adjusting the dps of the calling context + # hopefully the caller doesn't monkey around with it + # between calling this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + self.V = self._coeff() + self.p = self.ctx.matrix(self.ctx.arange(1, M+1))*self.ctx.ln2/self.t + + # NB: p is real (mpf) + + def _coeff(self): + r"""Salzer summation weights (aka, "Stehfest coefficients") + only depend on the approximation order (M) and the precision""" + + M = self.degree + M2 = int(M/2) # checked earlier that M is even + + V = self.ctx.matrix(M, 1) + + # Salzer summation weights + # get very large in magnitude and oscillate in sign, + # if the precision is not high enough, there will be + # catastrophic cancellation + for k in range(1, M+1): + z = self.ctx.matrix(min(k, M2)+1, 1) + for j in range(int((k+1)/2), min(k, M2)+1): + z[j] = (self.ctx.power(j, M2)*self.ctx.fac(2*j)/ + (self.ctx.fac(M2-j)*self.ctx.fac(j)* + self.ctx.fac(j-1)*self.ctx.fac(k-j)* + self.ctx.fac(2*j-k))) + V[k-1] = self.ctx.power(-1, k+M2)*self.ctx.fsum(z) + + return V + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Compute time-domain Stehfest algorithm solution. + + .. math :: + + f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left( + p_k \right) + + where + + .. math :: + + V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor} + \frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \, + \left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!} + + As the degree increases, the abscissa (`p_k`) only increase + linearly towards `\infty`, but the Stehfest coefficients + (`V_k`) alternate in sign and increase rapidly in sign, + requiring high precision to prevent overflow or loss of + significance when evaluating the sum. + + **References** + + 1. Widder, D. (1941). *The Laplace Transform*. Princeton. + 2. Stehfest, H. (1970). Algorithm 368: numerical inversion of + Laplace transforms. *Communications of the ACM* 13(1):47-49, + http://dx.doi.org/10.1145/361953.361969 + + """ + + # required + self.t = self.ctx.convert(t) + + # assume fp was computed from p matrix returned from + # calc_laplace_parameter(), so is already + # a list or matrix of mpmath 'mpf' types + + result = self.ctx.fdot(self.V, fp)*self.ctx.ln2/self.t + + # setting dps back to value when calc_laplace_parameter was called + if not manual_prec: + self.ctx.dps = self.dps_orig + + # ignore any small imaginary part + return result.real + + +# **************************************** + +class deHoog(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""the de Hoog, Knight & Stokes algorithm is an + accelerated form of the Fourier series numerical + inverse Laplace transform algorithms. + + .. math :: + + p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1 + + where + + .. math :: + + \gamma = \alpha - \frac{\log \mathrm{tol}}{2T}, + + `j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time, + `\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the + rightmost pole or singularity, which is chosen based on the + desired accuracy (assuming the rightmost singularity is 0), + and `\mathrm{tol}=10\alpha` is the desired tolerance, which is + chosen in relation to `\alpha`.` + + When increasing the degree, the abscissa increase towards + `j\infty`, but more slowly than the fixed Talbot + algorithm. The de Hoog et al. algorithm typically does better + with oscillatory functions of time, and less well-behaved + functions. The method tends to be slower than the Talbot and + Stehfest algorithsm, especially so at very high precision + (e.g., `>500` digits precision). + + """ + + # required + # ------------------------------ + self.t = self.ctx.convert(t) + + # optional + # ------------------------------ + self.tmax = kwargs.get('tmax', self.t) + + # empirical relationships used here based on a linear fit of + # requested and delivered dps for exponentially decaying time + # functions for requested dps up to 512. + + if 'degree' in kwargs: + self.degree = kwargs['degree'] + self.dps_goal = int(1.38*self.degree) + else: + self.dps_goal = int(self.ctx.dps*1.36) + self.degree = max(10, self.dps_goal) + + # 2*M+1 terms in approximation + M = self.degree + + # adjust alpha component of abscissa of convergence for higher + # precision + tmp = self.ctx.power(10.0, -self.dps_goal) + self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) + + # desired tolerance (here simply related to alpha) + self.tol = self.ctx.convert(kwargs.get('tol', self.alpha*10.0)) + self.np = 2*self.degree+1 # number of terms in approximation + + # this is adjusting the dps of the calling context + # hopefully the caller doesn't monkey around with it + # between calling this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + # scaling factor (likely tun-able, but 2 is typical) + self.scale = kwargs.get('scale', 2) + self.T = self.ctx.convert(kwargs.get('T', self.scale*self.tmax)) + + self.p = self.ctx.matrix(2*M+1, 1) + self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T) + self.p = (self.gamma + self.ctx.pi* + self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j) + + # NB: p is complex (mpc) + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Calculate time-domain solution for + de Hoog, Knight & Stokes algorithm. + + The un-accelerated Fourier series approach is: + + .. math :: + + f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'} + \Re\left[\bar{f}\left( p_k \right) + e^{i\pi t/T} \right], + + where the prime on the summation indicates the first term is halved. + + This simplistic approach requires so many function evaluations + that it is not practical. Non-linear acceleration is + accomplished via Pade-approximation and an analytic expression + for the remainder of the continued fraction. See the original + paper (reference 2 below) a detailed description of the + numerical approach. + + **References** + + 1. Davies, B. (2005). *Integral Transforms and their + Applications*, Third Edition. Springer. + 2. de Hoog, F., J. Knight, A. Stokes (1982). An improved + method for numerical inversion of Laplace transforms. *SIAM + Journal of Scientific and Statistical Computing* 3:357-366, + http://dx.doi.org/10.1137/0903022 + + """ + + M = self.degree + np = self.np + T = self.T + + self.t = self.ctx.convert(t) + + # would it be useful to try re-using + # space between e&q and A&B? + e = self.ctx.zeros(np, M+1) + q = self.ctx.matrix(2*M, M) + d = self.ctx.matrix(np, 1) + A = self.ctx.zeros(np+1, 1) + B = self.ctx.ones(np+1, 1) + + # initialize Q-D table + e[:, 0] = 0.0 + 0j + q[0, 0] = fp[1]/(fp[0]/2) + for i in range(1, 2*M): + q[i, 0] = fp[i+1]/fp[i] + + # rhombus rule for filling triangular Q-D table (e & q) + for r in range(1, M+1): + # start with e, column 1, 0:2*M-2 + mr = 2*(M-r) + 1 + e[0:mr, r] = q[1:mr+1, r-1] - q[0:mr, r-1] + e[1:mr+1, r-1] + if not r == M: + rq = r+1 + mr = 2*(M-rq)+1 + 2 + for i in range(mr): + q[i, rq-1] = q[i+1, rq-2]*e[i+1, rq-1]/e[i, rq-1] + + # build up continued fraction coefficients (d) + d[0] = fp[0]/2 + for r in range(1, M+1): + d[2*r-1] = -q[0, r-1] # even terms + d[2*r] = -e[0, r] # odd terms + + # seed A and B for recurrence + A[0] = 0.0 + 0.0j + A[1] = d[0] + B[0:2] = 1.0 + 0.0j + + # base of the power series + z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn + + # coefficients of Pade approximation (A & B) + # using recurrence for all but last term + for i in range(1, 2*M): + A[i+1] = A[i] + d[i]*A[i-1]*z + B[i+1] = B[i] + d[i]*B[i-1]*z + + # "improved remainder" to continued fraction + brem = (1 + (d[2*M-1] - d[2*M])*z)/2 + # powm1(x,y) computes x^y - 1 more accurately near zero + rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem, + self.ctx.fraction(1, 2)) + + # last term of recurrence using new remainder + A[np] = A[2*M] + rem*A[2*M-1] + B[np] = B[2*M] + rem*B[2*M-1] + + # diagonal Pade approximation + # F=A/B represents accelerated trapezoid rule + result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real + + # setting dps back to value when calc_laplace_parameter was called + if not manual_prec: + self.ctx.dps = self.dps_orig + + return result + + +# **************************************** + +class Cohen(InverseLaplaceTransform): + + def calc_laplace_parameter(self, t, **kwargs): + r"""The Cohen algorithm accelerates the convergence of the nearly + alternating series resulting from the application of the trapezoidal + rule to the Bromwich contour inversion integral. + + .. math :: + + p_k = \frac{\gamma}{2 t} + \frac{\pi i k}{t} \qquad 0 \le k < M + + where + + .. math :: + + \gamma = \frac{2}{3} (d + \log(10) + \log(2 t)), + + `d = \mathrm{dps\_goal}`, which is chosen based on the desired + accuracy using the method developed in [1] to improve numerical + stability. The Cohen algorithm shows robustness similar to the de Hoog + et al. algorithm, but it is faster than the fixed Talbot algorithm. + + **Optional arguments** + + *degree* + integer order of the approximation (M = number of terms) + *alpha* + abscissa for `p_0` (controls the discretization error) + + The working precision will be increased according to a rule of + thumb. If 'degree' is not specified, the working precision and + degree are chosen to hopefully achieve the dps of the calling + context. If 'degree' is specified, the working precision is + chosen to achieve maximum resulting precision for the + specified degree. + + **References** + + 1. P. Glasserman, J. Ruiz-Mata (2006). Computing the credit loss + distribution in the Gaussian copula model: a comparison of methods. + *Journal of Credit Risk* 2(4):33-66, 10.21314/JCR.2006.057 + + """ + self.t = self.ctx.convert(t) + + if 'degree' in kwargs: + self.degree = kwargs['degree'] + self.dps_goal = int(1.5 * self.degree) + else: + self.dps_goal = int(self.ctx.dps * 1.74) + self.degree = max(22, int(1.31 * self.dps_goal)) + + M = self.degree + 1 + + # this is adjusting the dps of the calling context hopefully + # the caller doesn't monkey around with it between calling + # this routine and calc_time_domain_solution() + self.dps_orig = self.ctx.dps + self.ctx.dps = self.dps_goal + + ttwo = 2 * self.t + tmp = self.ctx.dps * self.ctx.log(10) + self.ctx.log(ttwo) + tmp = self.ctx.fraction(2, 3) * tmp + self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) + + # all but time-dependent part of p + a_t = self.alpha / ttwo + p_t = self.ctx.pi * 1j / self.t + + self.p = self.ctx.matrix(M, 1) + self.p[0] = a_t + + for i in range(1, M): + self.p[i] = a_t + i * p_t + + def calc_time_domain_solution(self, fp, t, manual_prec=False): + r"""Calculate time-domain solution for Cohen algorithm. + + The accelerated nearly alternating series is: + + .. math :: + + f(t, M) = \frac{e^{\gamma / 2}}{t} \left[\frac{1}{2} + \Re\left(\bar{f}\left(\frac{\gamma}{2t}\right) \right) - + \sum_{k=0}^{M-1}\frac{c_{M,k}}{d_M}\Re\left(\bar{f} + \left(\frac{\gamma + 2(k+1) \pi i}{2t}\right)\right)\right], + + where coefficients `\frac{c_{M, k}}{d_M}` are described in [1]. + + 1. H. Cohen, F. Rodriguez Villegas, D. Zagier (2000). Convergence + acceleration of alternating series. *Experiment. Math* 9(1):3-12 + + """ + self.t = self.ctx.convert(t) + + n = self.degree + M = n + 1 + + A = self.ctx.matrix(M, 1) + for i in range(M): + A[i] = fp[i].real + + d = (3 + self.ctx.sqrt(8)) ** n + d = (d + 1 / d) / 2 + b = -self.ctx.one + c = -d + s = 0 + + for k in range(n): + c = b - c + s = s + c * A[k + 1] + b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one)) + + result = self.ctx.exp(self.alpha / 2) / self.t * (A[0] / 2 - s / d) + + # setting dps back to value when calc_laplace_parameter was + # called, unless flag is set. + if not manual_prec: + self.ctx.dps = self.dps_orig + + return result + + +# **************************************** + +class LaplaceTransformInversionMethods(object): + def __init__(ctx, *args, **kwargs): + ctx._fixed_talbot = FixedTalbot(ctx) + ctx._stehfest = Stehfest(ctx) + ctx._de_hoog = deHoog(ctx) + ctx._cohen = Cohen(ctx) + + def invertlaplace(ctx, f, t, **kwargs): + r"""Computes the numerical inverse Laplace transform for a + Laplace-space function at a given time. The function being + evaluated is assumed to be a real-valued function of time. + + The user must supply a Laplace-space function `\bar{f}(p)`, + and a desired time at which to estimate the time-domain + solution `f(t)`. + + A few basic examples of Laplace-space functions with known + inverses (see references [1,2]) : + + .. math :: + + \mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p) + + .. math :: + + \mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t) + + .. math :: + + \bar{f}(p) = \frac{1}{(p+1)^2} + + .. math :: + + f(t) = t e^{-t} + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> tt = [0.001, 0.01, 0.1, 1, 10] + >>> fp = lambda p: 1/(p+1)**2 + >>> ft = lambda t: t*exp(-t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot') + (0.000999000499833375, 8.57923043561212e-20) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot') + (0.00990049833749168, 3.27007646698047e-19) + >>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot') + (0.090483741803596, -1.75215800052168e-18) + >>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot') + (0.367879441171442, 1.2428864009344e-17) + >>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot') + (0.000453999297624849, 4.04513489306658e-20) + + The methods also work for higher precision: + + >>> mp.dps = 100; mp.pretty = True + >>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15) + ('0.000999000499833375', '-4.96868310693356e-105') + >>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15) + ('0.00990049833749168', '1.23032291513122e-104') + + .. math :: + + \bar{f}(p) = \frac{1}{p^2+1} + + .. math :: + + f(t) = \mathrm{J}_0(t) + + >>> mp.dps = 15; mp.pretty = True + >>> fp = lambda p: 1/sqrt(p*p + 1) + >>> ft = lambda t: besselj(0,t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='dehoog') + (0.999999750000016, -6.09717765032273e-18) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='dehoog') + (0.99997500015625, -5.61756281076169e-17) + + .. math :: + + \bar{f}(p) = \frac{\log p}{p} + + .. math :: + + f(t) = -\gamma -\log t + + >>> mp.dps = 15; mp.pretty = True + >>> fp = lambda p: log(p)/p + >>> ft = lambda t: -euler-log(t) + >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest') + (6.3305396140806, -1.92126634837863e-16) + >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest') + (4.02795452108656, -4.81486093200704e-16) + + **Options** + + :func:`~mpmath.invertlaplace` recognizes the following optional + keywords valid for all methods: + + *method* + Chooses numerical inverse Laplace transform algorithm + (described below). + *degree* + Number of terms used in the approximation + + **Algorithms** + + Mpmath implements four numerical inverse Laplace transform + algorithms, attributed to: Talbot, Stehfest, and de Hoog, + Knight and Stokes. These can be selected by using + *method='talbot'*, *method='stehfest'*, *method='dehoog'* or + *method='cohen'* or by passing the classes *method=FixedTalbot*, + *method=Stehfest*, *method=deHoog*, or *method=Cohen*. The functions + :func:`~mpmath.invlaptalbot`, :func:`~mpmath.invlapstehfest`, + :func:`~mpmath.invlapdehoog`, and :func:`~mpmath.invlapcohen` + are also available as shortcuts. + + All four algorithms implement a heuristic balance between the + requested precision and the precision used internally for the + calculations. This has been tuned for a typical exponentially + decaying function and precision up to few hundred decimal + digits. + + The Laplace transform converts the variable time (i.e., along + a line) into a parameter given by the right half of the + complex `p`-plane. Singularities, poles, and branch cuts in + the complex `p`-plane contain all the information regarding + the time behavior of the corresponding function. Any numerical + method must therefore sample `p`-plane "close enough" to the + singularities to accurately characterize them, while not + getting too close to have catastrophic cancellation, overflow, + or underflow issues. Most significantly, if one or more of the + singularities in the `p`-plane is not on the left side of the + Bromwich contour, its effects will be left out of the computed + solution, and the answer will be completely wrong. + + *Talbot* + + The fixed Talbot method is high accuracy and fast, but the + method can catastrophically fail for certain classes of time-domain + behavior, including a Heaviside step function for positive + time (e.g., `H(t-2)`), or some oscillatory behaviors. The + Talbot method usually has adjustable parameters, but the + "fixed" variety implemented here does not. This method + deforms the Bromwich integral contour in the shape of a + parabola towards `-\infty`, which leads to problems + when the solution has a decaying exponential in it (e.g., a + Heaviside step function is equivalent to multiplying by a + decaying exponential in Laplace space). + + *Stehfest* + + The Stehfest algorithm only uses abscissa along the real axis + of the complex `p`-plane to estimate the time-domain + function. Oscillatory time-domain functions have poles away + from the real axis, so this method does not work well with + oscillatory functions, especially high-frequency ones. This + method also depends on summation of terms in a series that + grows very large, and will have catastrophic cancellation + during summation if the working precision is too low. + + *de Hoog et al.* + + The de Hoog, Knight, and Stokes method is essentially a + Fourier-series quadrature-type approximation to the Bromwich + contour integral, with non-linear series acceleration and an + analytical expression for the remainder term. This method is + typically one of the most robust. This method also involves the + greatest amount of overhead, so it is typically the slowest of the + four methods at high precision. + + *Cohen* + + The Cohen method is a trapezoidal rule approximation to the Bromwich + contour integral, with linear acceleration for alternating + series. This method is as robust as the de Hoog et al method and the + fastest of the four methods at high precision, and is therefore the + default method. + + **Singularities** + + All numerical inverse Laplace transform methods have problems + at large time when the Laplace-space function has poles, + singularities, or branch cuts to the right of the origin in + the complex plane. For simple poles in `\bar{f}(p)` at the + `p`-plane origin, the time function is constant in time (e.g., + `\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at + `p=0`). A pole in `\bar{f}(p)` to the left of the origin is a + decreasing function of time (e.g., `\mathcal{L}\left\lbrace + e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and + a pole to the right of the origin leads to an increasing + function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4} + \right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When + singularities occur off the real `p` axis, the time-domain + function is oscillatory. For example `\mathcal{L}\left\lbrace + \mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut + starting at `p=j=\sqrt{-1}` and is a decaying oscillatory + function, This range of behaviors is illustrated in Duffy [3] + Figure 4.10.4, p. 228. + + In general as `p \rightarrow \infty` `t \rightarrow 0` and + vice-versa. All numerical inverse Laplace transform methods + require their abscissa to shift closer to the origin for + larger times. If the abscissa shift left of the rightmost + singularity in the Laplace domain, the answer will be + completely wrong (the effect of singularities to the right of + the Bromwich contour are not included in the results). + + For example, the following exponentially growing function has + a pole at `p=3`: + + .. math :: + + \bar{f}(p)=\frac{1}{p^2-9} + + .. math :: + + f(t)=\frac{1}{3}\sinh 3t + + >>> mp.dps = 15; mp.pretty = True + >>> fp = lambda p: 1/(p*p-9) + >>> ft = lambda t: sinh(3*t)/3 + >>> tt = [0.01,0.1,1.0,10.0] + >>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot') + (0.0100015000675014, 0.0100015000675014) + >>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot') + (0.101506764482381, 0.101506764482381) + >>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot') + (3.33929164246997, 3.33929164246997) + >>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot') + (1781079096920.74, -1.61331069624091e-14) + + **References** + + 1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4) + 2. Cohen, A.M. (2007). Numerical Methods for Laplace Transform + Inversion, Springer. + 3. Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press. + + **Numerical Inverse Laplace Transform Reviews** + + 1. Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical + inversion of the Laplace transform: Applications to Biology, + Economics, Engineering, and Physics*. Elsevier. + 2. Davies, B., B. Martin (1979). Numerical inversion of the + Laplace transform: a survey and comparison of methods. *Journal + of Computational Physics* 33:1-32, + http://dx.doi.org/10.1016/0021-9991(79)90025-1 + 3. Duffy, D.G. (1993). On the numerical inversion of Laplace + transforms: Comparison of three new methods on characteristic + problems from applications. *ACM Transactions on Mathematical + Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788 + 4. Kuhlman, K.L., (2013). Review of Inverse Laplace Transform + Algorithms for Laplace-Space Numerical Approaches, *Numerical + Algorithms*, 63(2):339-355. + http://dx.doi.org/10.1007/s11075-012-9625-3 + + """ + + rule = kwargs.get('method', 'cohen') + if type(rule) is str: + lrule = rule.lower() + if lrule == 'talbot': + rule = ctx._fixed_talbot + elif lrule == 'stehfest': + rule = ctx._stehfest + elif lrule == 'dehoog': + rule = ctx._de_hoog + elif rule == 'cohen': + rule = ctx._cohen + else: + raise ValueError("unknown invlap algorithm: %s" % rule) + else: + rule = rule(ctx) + + # determine the vector of Laplace-space parameter + # needed for the requested method and desired time + rule.calc_laplace_parameter(t, **kwargs) + + # compute the Laplace-space function evalutations + # at the required abscissa. + fp = [f(p) for p in rule.p] + + # compute the time-domain solution from the + # Laplace-space function evaluations + return rule.calc_time_domain_solution(fp, t) + + # shortcuts for the above function for specific methods + def invlaptalbot(ctx, *args, **kwargs): + kwargs['method'] = 'talbot' + return ctx.invertlaplace(*args, **kwargs) + + def invlapstehfest(ctx, *args, **kwargs): + kwargs['method'] = 'stehfest' + return ctx.invertlaplace(*args, **kwargs) + + def invlapdehoog(ctx, *args, **kwargs): + kwargs['method'] = 'dehoog' + return ctx.invertlaplace(*args, **kwargs) + + def invlapcohen(ctx, *args, **kwargs): + kwargs['method'] = 'cohen' + return ctx.invertlaplace(*args, **kwargs) + + +# **************************************** + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/odes.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/odes.py new file mode 100644 index 0000000000000000000000000000000000000000..ac6bd3cb056c8841e6cf77ba4d08a0b1abb2f2c9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/odes.py @@ -0,0 +1,288 @@ +from bisect import bisect +from ..libmp.backend import xrange + +class ODEMethods(object): + pass + +def ode_taylor(ctx, derivs, x0, y0, tol_prec, n): + h = tol = ctx.ldexp(1, -tol_prec) + dim = len(y0) + xs = [x0] + ys = [y0] + x = x0 + y = y0 + orig = ctx.prec + try: + ctx.prec = orig*(1+n) + # Use n steps with Euler's method to get + # evaluation points for derivatives + for i in range(n): + fxy = derivs(x, y) + y = [y[i]+h*fxy[i] for i in xrange(len(y))] + x += h + xs.append(x) + ys.append(y) + # Compute derivatives + ser = [[] for d in range(dim)] + for j in range(n+1): + s = [0]*dim + b = (-1) ** (j & 1) + k = 1 + for i in range(j+1): + for d in range(dim): + s[d] += b * ys[i][d] + b = (b * (j-k+1)) // (-k) + k += 1 + scale = h**(-j) / ctx.fac(j) + for d in range(dim): + s[d] = s[d] * scale + ser[d].append(s[d]) + finally: + ctx.prec = orig + # Estimate radius for which we can get full accuracy. + # XXX: do this right for zeros + radius = ctx.one + for ts in ser: + if ts[-1]: + radius = min(radius, ctx.nthroot(tol/abs(ts[-1]), n)) + radius /= 2 # XXX + return ser, x0+radius + +def odefun(ctx, F, x0, y0, tol=None, degree=None, method='taylor', verbose=False): + r""" + Returns a function `y(x) = [y_0(x), y_1(x), \ldots, y_n(x)]` + that is a numerical solution of the `n+1`-dimensional first-order + ordinary differential equation (ODE) system + + .. math :: + + y_0'(x) = F_0(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + y_1'(x) = F_1(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + \vdots + + y_n'(x) = F_n(x, [y_0(x), y_1(x), \ldots, y_n(x)]) + + The derivatives are specified by the vector-valued function + *F* that evaluates + `[y_0', \ldots, y_n'] = F(x, [y_0, \ldots, y_n])`. + The initial point `x_0` is specified by the scalar argument *x0*, + and the initial value `y(x_0) = [y_0(x_0), \ldots, y_n(x_0)]` is + specified by the vector argument *y0*. + + For convenience, if the system is one-dimensional, you may optionally + provide just a scalar value for *y0*. In this case, *F* should accept + a scalar *y* argument and return a scalar. The solution function + *y* will return scalar values instead of length-1 vectors. + + Evaluation of the solution function `y(x)` is permitted + for any `x \ge x_0`. + + A high-order ODE can be solved by transforming it into first-order + vector form. This transformation is described in standard texts + on ODEs. Examples will also be given below. + + **Options, speed and accuracy** + + By default, :func:`~mpmath.odefun` uses a high-order Taylor series + method. For reasonably well-behaved problems, the solution will + be fully accurate to within the working precision. Note that + *F* must be possible to evaluate to very high precision + for the generation of Taylor series to work. + + To get a faster but less accurate solution, you can set a large + value for *tol* (which defaults roughly to *eps*). If you just + want to plot the solution or perform a basic simulation, + *tol = 0.01* is likely sufficient. + + The *degree* argument controls the degree of the solver (with + *method='taylor'*, this is the degree of the Taylor series + expansion). A higher degree means that a longer step can be taken + before a new local solution must be generated from *F*, + meaning that fewer steps are required to get from `x_0` to a given + `x_1`. On the other hand, a higher degree also means that each + local solution becomes more expensive (i.e., more evaluations of + *F* are required per step, and at higher precision). + + The optimal setting therefore involves a tradeoff. Generally, + decreasing the *degree* for Taylor series is likely to give faster + solution at low precision, while increasing is likely to be better + at higher precision. + + The function + object returned by :func:`~mpmath.odefun` caches the solutions at all step + points and uses polynomial interpolation between step points. + Therefore, once `y(x_1)` has been evaluated for some `x_1`, + `y(x)` can be evaluated very quickly for any `x_0 \le x \le x_1`. + and continuing the evaluation up to `x_2 > x_1` is also fast. + + **Examples of first-order ODEs** + + We will solve the standard test problem `y'(x) = y(x), y(0) = 1` + which has explicit solution `y(x) = \exp(x)`:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> f = odefun(lambda x, y: y, 0, 1) + >>> for x in [0, 1, 2.5]: + ... print((f(x), exp(x))) + ... + (1.0, 1.0) + (2.71828182845905, 2.71828182845905) + (12.1824939607035, 12.1824939607035) + + The solution with high precision:: + + >>> mp.dps = 50 + >>> f = odefun(lambda x, y: y, 0, 1) + >>> f(1) + 2.7182818284590452353602874713526624977572470937 + >>> exp(1) + 2.7182818284590452353602874713526624977572470937 + + Using the more general vectorized form, the test problem + can be input as (note that *f* returns a 1-element vector):: + + >>> mp.dps = 15 + >>> f = odefun(lambda x, y: [y[0]], 0, [1]) + >>> f(1) + [2.71828182845905] + + :func:`~mpmath.odefun` can solve nonlinear ODEs, which are generally + impossible (and at best difficult) to solve analytically. As + an example of a nonlinear ODE, we will solve `y'(x) = x \sin(y(x))` + for `y(0) = \pi/2`. An exact solution happens to be known + for this problem, and is given by + `y(x) = 2 \tan^{-1}\left(\exp\left(x^2/2\right)\right)`:: + + >>> f = odefun(lambda x, y: x*sin(y), 0, pi/2) + >>> for x in [2, 5, 10]: + ... print((f(x), 2*atan(exp(mpf(x)**2/2)))) + ... + (2.87255666284091, 2.87255666284091) + (3.14158520028345, 3.14158520028345) + (3.14159265358979, 3.14159265358979) + + If `F` is independent of `y`, an ODE can be solved using direct + integration. We can therefore obtain a reference solution with + :func:`~mpmath.quad`:: + + >>> f = lambda x: (1+x**2)/(1+x**3) + >>> g = odefun(lambda x, y: f(x), pi, 0) + >>> g(2*pi) + 0.72128263801696 + >>> quad(f, [pi, 2*pi]) + 0.72128263801696 + + **Examples of second-order ODEs** + + We will solve the harmonic oscillator equation `y''(x) + y(x) = 0`. + To do this, we introduce the helper functions `y_0 = y, y_1 = y_0'` + whereby the original equation can be written as `y_1' + y_0' = 0`. Put + together, we get the first-order, two-dimensional vector ODE + + .. math :: + + \begin{cases} + y_0' = y_1 \\ + y_1' = -y_0 + \end{cases} + + To get a well-defined IVP, we need two initial values. With + `y(0) = y_0(0) = 1` and `-y'(0) = y_1(0) = 0`, the problem will of + course be solved by `y(x) = y_0(x) = \cos(x)` and + `-y'(x) = y_1(x) = \sin(x)`. We check this:: + + >>> f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0]) + >>> for x in [0, 1, 2.5, 10]: + ... nprint(f(x), 15) + ... nprint([cos(x), sin(x)], 15) + ... print("---") + ... + [1.0, 0.0] + [1.0, 0.0] + --- + [0.54030230586814, 0.841470984807897] + [0.54030230586814, 0.841470984807897] + --- + [-0.801143615546934, 0.598472144103957] + [-0.801143615546934, 0.598472144103957] + --- + [-0.839071529076452, -0.54402111088937] + [-0.839071529076452, -0.54402111088937] + --- + + Note that we get both the sine and the cosine solutions + simultaneously. + + **TODO** + + * Better automatic choice of degree and step size + * Make determination of Taylor series convergence radius + more robust + * Allow solution for `x < x_0` + * Allow solution for complex `x` + * Test for difficult (ill-conditioned) problems + * Implement Runge-Kutta and other algorithms + + """ + if tol: + tol_prec = int(-ctx.log(tol, 2))+10 + else: + tol_prec = ctx.prec+10 + degree = degree or (3 + int(3*ctx.dps/2.)) + workprec = ctx.prec + 40 + try: + len(y0) + return_vector = True + except TypeError: + F_ = F + F = lambda x, y: [F_(x, y[0])] + y0 = [y0] + return_vector = False + ser, xb = ode_taylor(ctx, F, x0, y0, tol_prec, degree) + series_boundaries = [x0, xb] + series_data = [(ser, x0, xb)] + # We will be working with vectors of Taylor series + def mpolyval(ser, a): + return [ctx.polyval(s[::-1], a) for s in ser] + # Find nearest expansion point; compute if necessary + def get_series(x): + if x < x0: + raise ValueError + n = bisect(series_boundaries, x) + if n < len(series_boundaries): + return series_data[n-1] + while 1: + ser, xa, xb = series_data[-1] + if verbose: + print("Computing Taylor series for [%f, %f]" % (xa, xb)) + y = mpolyval(ser, xb-xa) + xa = xb + ser, xb = ode_taylor(ctx, F, xb, y, tol_prec, degree) + series_boundaries.append(xb) + series_data.append((ser, xa, xb)) + if x <= xb: + return series_data[-1] + # Evaluation function + def interpolant(x): + x = ctx.convert(x) + orig = ctx.prec + try: + ctx.prec = workprec + ser, xa, xb = get_series(x) + y = mpolyval(ser, x-xa) + finally: + ctx.prec = orig + if return_vector: + return [+yk for yk in y] + else: + return +y[0] + return interpolant + +ODEMethods.odefun = odefun + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/optimization.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/optimization.py new file mode 100644 index 0000000000000000000000000000000000000000..69724f78336ee24856366db48917a47108b1b416 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/optimization.py @@ -0,0 +1,1102 @@ +from __future__ import print_function + +from copy import copy + +from ..libmp.backend import xrange + +class OptimizationMethods(object): + def __init__(ctx): + pass + +############## +# 1D-SOLVERS # +############## + +class Newton: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0 close to the root. + + Pro: + + * converges fast + * sometimes more robust than secant with bad second starting point + + Contra: + + * converges slowly for multiple roots + * needs first derivative + * 2 function evaluations per iteration + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + else: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.f = f + if not 'df' in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + + def __iter__(self): + f = self.f + df = self.df + x0 = self.x0 + while True: + x1 = x0 - f(x0) / df(x0) + error = abs(x1 - x0) + x0 = x1 + yield (x1, error) + +class Secant: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0 and x1 close to the root. + x1 defaults to x0 + 0.25. + + Pro: + + * converges fast + + Contra: + + * converges slowly for multiple roots + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + self.x1 = self.x0 + 0.25 + elif len(x0) == 2: + self.x0 = x0[0] + self.x1 = x0[1] + else: + raise ValueError('expected 1 or 2 starting points, got %i' % len(x0)) + self.f = f + + def __iter__(self): + f = self.f + x0 = self.x0 + x1 = self.x1 + f0 = f(x0) + while True: + f1 = f(x1) + l = x1 - x0 + if not l: + break + s = (f1 - f0) / l + if not s: + break + x0, x1 = x1, x1 - f1/s + f0 = f1 + yield x1, abs(l) + +class MNewton: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting point x0 close to the root. + Uses modified Newton's method that converges fast regardless of the + multiplicity of the root. + + Pro: + + * converges fast for multiple roots + + Contra: + + * needs first and second derivative of f + * 3 function evaluations per iteration + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if not 'df' in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + if not 'd2f' in kwargs: + def d2f(x): + return self.ctx.diff(df, x) + else: + d2f = kwargs['df'] + self.d2f = d2f + + def __iter__(self): + x = self.x0 + f = self.f + df = self.df + d2f = self.d2f + while True: + prevx = x + fx = f(x) + if fx == 0: + break + dfx = df(x) + d2fx = d2f(x) + # x = x - F(x)/F'(x) with F(x) = f(x)/f'(x) + x -= fx / (dfx - fx * d2fx / dfx) + error = abs(x - prevx) + yield x, error + +class Halley: + """ + 1d-solver generating pairs of approximative root and error. + + Needs a starting point x0 close to the root. + Uses Halley's method with cubic convergence rate. + + Pro: + + * converges even faster the Newton's method + * useful when computing with *many* digits + + Contra: + + * needs first and second derivative of f + * 3 function evaluations per iteration + * converges slowly for multiple roots + """ + + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if not 'df' in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + if not 'd2f' in kwargs: + def d2f(x): + return self.ctx.diff(df, x) + else: + d2f = kwargs['df'] + self.d2f = d2f + + def __iter__(self): + x = self.x0 + f = self.f + df = self.df + d2f = self.d2f + while True: + prevx = x + fx = f(x) + dfx = df(x) + d2fx = d2f(x) + x -= 2*fx*dfx / (2*dfx**2 - fx*d2fx) + error = abs(x - prevx) + yield x, error + +class Muller: + """ + 1d-solver generating pairs of approximative root and error. + + Needs starting points x0, x1 and x2 close to the root. + x1 defaults to x0 + 0.25; x2 to x1 + 0.25. + Uses Muller's method that converges towards complex roots. + + Pro: + + * converges fast (somewhat faster than secant) + * can find complex roots + + Contra: + + * converges slowly for multiple roots + * may have complex values for real starting points and real roots + + http://en.wikipedia.org/wiki/Muller's_method + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) == 1: + self.x0 = x0[0] + self.x1 = self.x0 + 0.25 + self.x2 = self.x1 + 0.25 + elif len(x0) == 2: + self.x0 = x0[0] + self.x1 = x0[1] + self.x2 = self.x1 + 0.25 + elif len(x0) == 3: + self.x0 = x0[0] + self.x1 = x0[1] + self.x2 = x0[2] + else: + raise ValueError('expected 1, 2 or 3 starting points, got %i' + % len(x0)) + self.f = f + self.verbose = kwargs['verbose'] + + def __iter__(self): + f = self.f + x0 = self.x0 + x1 = self.x1 + x2 = self.x2 + fx0 = f(x0) + fx1 = f(x1) + fx2 = f(x2) + while True: + # TODO: maybe refactoring with function for divided differences + # calculate divided differences + fx2x1 = (fx1 - fx2) / (x1 - x2) + fx2x0 = (fx0 - fx2) / (x0 - x2) + fx1x0 = (fx0 - fx1) / (x0 - x1) + w = fx2x1 + fx2x0 - fx1x0 + fx2x1x0 = (fx1x0 - fx2x1) / (x0 - x2) + if w == 0 and fx2x1x0 == 0: + if self.verbose: + print('canceled with') + print('x0 =', x0, ', x1 =', x1, 'and x2 =', x2) + break + x0 = x1 + fx0 = fx1 + x1 = x2 + fx1 = fx2 + # denominator should be as large as possible => choose sign + r = self.ctx.sqrt(w**2 - 4*fx2*fx2x1x0) + if abs(w - r) > abs(w + r): + r = -r + x2 -= 2*fx2 / (w + r) + fx2 = f(x2) + error = abs(x2 - x1) + yield x2, error + +# TODO: consider raising a ValueError when there's no sign change in a and b +class Bisection: + """ + 1d-solver generating pairs of approximative root and error. + + Uses bisection method to find a root of f in [a, b]. + Might fail for multiple roots (needs sign change). + + Pro: + + * robust and reliable + + Contra: + + * converges slowly + * needs sign change + """ + maxsteps = 100 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.f = f + self.a = x0[0] + self.b = x0[1] + + def __iter__(self): + f = self.f + a = self.a + b = self.b + l = b - a + fb = f(b) + while True: + m = self.ctx.ldexp(a + b, -1) + fm = f(m) + sign = fm * fb + if sign < 0: + a = m + elif sign > 0: + b = m + fb = fm + else: + yield m, self.ctx.zero + l /= 2 + yield (a + b)/2, abs(l) + +def _getm(method): + """ + Return a function to calculate m for Illinois-like methods. + """ + if method == 'illinois': + def getm(fz, fb): + return 0.5 + elif method == 'pegasus': + def getm(fz, fb): + return fb/(fb + fz) + elif method == 'anderson': + def getm(fz, fb): + m = 1 - fz/fb + if m > 0: + return m + else: + return 0.5 + else: + raise ValueError("method '%s' not recognized" % method) + return getm + +class Illinois: + """ + 1d-solver generating pairs of approximative root and error. + + Uses Illinois method or similar to find a root of f in [a, b]. + Might fail for multiple roots (needs sign change). + Combines bisect with secant (improved regula falsi). + + The only difference between the methods is the scaling factor m, which is + used to ensure convergence (you can choose one using the 'method' keyword): + + Illinois method ('illinois'): + m = 0.5 + + Pegasus method ('pegasus'): + m = fb/(fb + fz) + + Anderson-Bjoerk method ('anderson'): + m = 1 - fz/fb if positive else 0.5 + + Pro: + + * converges very fast + + Contra: + + * has problems with multiple roots + * needs sign change + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.a = x0[0] + self.b = x0[1] + self.f = f + self.tol = kwargs['tol'] + self.verbose = kwargs['verbose'] + self.method = kwargs.get('method', 'illinois') + self.getm = _getm(self.method) + if self.verbose: + print('using %s method' % self.method) + + def __iter__(self): + method = self.method + f = self.f + a = self.a + b = self.b + fa = f(a) + fb = f(b) + m = None + while True: + l = b - a + if l == 0: + break + s = (fb - fa) / l + z = a - fa/s + fz = f(z) + if abs(fz) < self.tol: + # TODO: better condition (when f is very flat) + if self.verbose: + print('canceled with z =', z) + yield z, l + break + if fz * fb < 0: # root in [z, b] + a = b + fa = fb + b = z + fb = fz + else: # root in [a, z] + m = self.getm(fz, fb) + b = z + fb = fz + fa = m*fa # scale down to ensure convergence + if self.verbose and m and not method == 'illinois': + print('m:', m) + yield (a + b)/2, abs(l) + +def Pegasus(*args, **kwargs): + """ + 1d-solver generating pairs of approximative root and error. + + Uses Pegasus method to find a root of f in [a, b]. + Wrapper for illinois to use method='pegasus'. + """ + kwargs['method'] = 'pegasus' + return Illinois(*args, **kwargs) + +def Anderson(*args, **kwargs): + """ + 1d-solver generating pairs of approximative root and error. + + Uses Anderson-Bjoerk method to find a root of f in [a, b]. + Wrapper for illinois to use method='pegasus'. + """ + kwargs['method'] = 'anderson' + return Illinois(*args, **kwargs) + +# TODO: check whether it's possible to combine it with Illinois stuff +class Ridder: + """ + 1d-solver generating pairs of approximative root and error. + + Ridders' method to find a root of f in [a, b]. + Is told to perform as well as Brent's method while being simpler. + + Pro: + + * very fast + * simpler than Brent's method + + Contra: + + * two function evaluations per step + * has problems with multiple roots + * needs sign change + + http://en.wikipedia.org/wiki/Ridders'_method + """ + maxsteps = 30 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + self.f = f + if len(x0) != 2: + raise ValueError('expected interval of 2 points, got %i' % len(x0)) + self.x1 = x0[0] + self.x2 = x0[1] + self.verbose = kwargs['verbose'] + self.tol = kwargs['tol'] + + def __iter__(self): + ctx = self.ctx + f = self.f + x1 = self.x1 + fx1 = f(x1) + x2 = self.x2 + fx2 = f(x2) + while True: + x3 = 0.5*(x1 + x2) + fx3 = f(x3) + x4 = x3 + (x3 - x1) * ctx.sign(fx1 - fx2) * fx3 / ctx.sqrt(fx3**2 - fx1*fx2) + fx4 = f(x4) + if abs(fx4) < self.tol: + # TODO: better condition (when f is very flat) + if self.verbose: + print('canceled with f(x4) =', fx4) + yield x4, abs(x1 - x2) + break + if fx4 * fx2 < 0: # root in [x4, x2] + x1 = x4 + fx1 = fx4 + else: # root in [x1, x4] + x2 = x4 + fx2 = fx4 + error = abs(x1 - x2) + yield (x1 + x2)/2, error + +class ANewton: + """ + EXPERIMENTAL 1d-solver generating pairs of approximative root and error. + + Uses Newton's method modified to use Steffensens method when convergence is + slow. (I.e. for multiple roots.) + """ + maxsteps = 20 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + if not len(x0) == 1: + raise ValueError('expected 1 starting point, got %i' % len(x0)) + self.x0 = x0[0] + self.f = f + if not 'df' in kwargs: + def df(x): + return self.ctx.diff(f, x) + else: + df = kwargs['df'] + self.df = df + def phi(x): + return x - f(x) / df(x) + self.phi = phi + self.verbose = kwargs['verbose'] + + def __iter__(self): + x0 = self.x0 + f = self.f + df = self.df + phi = self.phi + error = 0 + counter = 0 + while True: + prevx = x0 + try: + x0 = phi(x0) + except ZeroDivisionError: + if self.verbose: + print('ZeroDivisionError: canceled with x =', x0) + break + preverror = error + error = abs(prevx - x0) + # TODO: decide not to use convergence acceleration + if error and abs(error - preverror) / error < 1: + if self.verbose: + print('converging slowly') + counter += 1 + if counter >= 3: + # accelerate convergence + phi = steffensen(phi) + counter = 0 + if self.verbose: + print('accelerating convergence') + yield x0, error + +# TODO: add Brent + +############################ +# MULTIDIMENSIONAL SOLVERS # +############################ + +def jacobian(ctx, f, x): + """ + Calculate the Jacobian matrix of a function at the point x0. + + This is the first derivative of a vectorial function: + + f : R^m -> R^n with m >= n + """ + x = ctx.matrix(x) + h = ctx.sqrt(ctx.eps) + fx = ctx.matrix(f(*x)) + m = len(fx) + n = len(x) + J = ctx.matrix(m, n) + for j in xrange(n): + xj = x.copy() + xj[j] += h + Jj = (ctx.matrix(f(*xj)) - fx) / h + for i in xrange(m): + J[i,j] = Jj[i] + return J + +# TODO: test with user-specified jacobian matrix +class MDNewton: + """ + Find the root of a vector function numerically using Newton's method. + + f is a vector function representing a nonlinear equation system. + + x0 is the starting point close to the root. + + J is a function returning the Jacobian matrix for a point. + + Supports overdetermined systems. + + Use the 'norm' keyword to specify which norm to use. Defaults to max-norm. + The function to calculate the Jacobian matrix can be given using the + keyword 'J'. Otherwise it will be calculated numerically. + + Please note that this method converges only locally. Especially for high- + dimensional systems it is not trivial to find a good starting point being + close enough to the root. + + It is recommended to use a faster, low-precision solver from SciPy [1] or + OpenOpt [2] to get an initial guess. Afterwards you can use this method for + root-polishing to any precision. + + [1] http://scipy.org + + [2] http://openopt.org/Welcome + """ + maxsteps = 10 + + def __init__(self, ctx, f, x0, **kwargs): + self.ctx = ctx + self.f = f + if isinstance(x0, (tuple, list)): + x0 = ctx.matrix(x0) + assert x0.cols == 1, 'need a vector' + self.x0 = x0 + if 'J' in kwargs: + self.J = kwargs['J'] + else: + def J(*x): + return ctx.jacobian(f, x) + self.J = J + self.norm = kwargs['norm'] + self.verbose = kwargs['verbose'] + + def __iter__(self): + f = self.f + x0 = self.x0 + norm = self.norm + J = self.J + fx = self.ctx.matrix(f(*x0)) + fxnorm = norm(fx) + cancel = False + while not cancel: + # get direction of descent + fxn = -fx + Jx = J(*x0) + s = self.ctx.lu_solve(Jx, fxn) + if self.verbose: + print('Jx:') + print(Jx) + print('s:', s) + # damping step size TODO: better strategy (hard task) + l = self.ctx.one + x1 = x0 + s + while True: + if x1 == x0: + if self.verbose: + print("canceled, won't get more excact") + cancel = True + break + fx = self.ctx.matrix(f(*x1)) + newnorm = norm(fx) + if newnorm < fxnorm: + # new x accepted + fxnorm = newnorm + x0 = x1 + break + l /= 2 + x1 = x0 + l*s + yield (x0, fxnorm) + +############# +# UTILITIES # +############# + +str2solver = {'newton':Newton, 'secant':Secant, 'mnewton':MNewton, + 'halley':Halley, 'muller':Muller, 'bisect':Bisection, + 'illinois':Illinois, 'pegasus':Pegasus, 'anderson':Anderson, + 'ridder':Ridder, 'anewton':ANewton, 'mdnewton':MDNewton} + +def findroot(ctx, f, x0, solver='secant', tol=None, verbose=False, verify=True, **kwargs): + r""" + Find an approximate solution to `f(x) = 0`, using *x0* as starting point or + interval for *x*. + + Multidimensional overdetermined systems are supported. + You can specify them using a function or a list of functions. + + Mathematically speaking, this function returns `x` such that + `|f(x)|^2 \leq \mathrm{tol}` is true within the current working precision. + If the computed value does not meet this criterion, an exception is raised. + This exception can be disabled with *verify=False*. + + For interval arithmetic (``iv.findroot()``), please note that + the returned interval ``x`` is not guaranteed to contain `f(x)=0`! + It is only some `x` for which `|f(x)|^2 \leq \mathrm{tol}` certainly holds + regardless of numerical error. This may be improved in the future. + + **Arguments** + + *f* + one dimensional function + *x0* + starting point, several starting points or interval (depends on solver) + *tol* + the returned solution has an error smaller than this + *verbose* + print additional information for each iteration if true + *verify* + verify the solution and raise a ValueError if `|f(x)|^2 > \mathrm{tol}` + *solver* + a generator for *f* and *x0* returning approximative solution and error + *maxsteps* + after how many steps the solver will cancel + *df* + first derivative of *f* (used by some solvers) + *d2f* + second derivative of *f* (used by some solvers) + *multidimensional* + force multidimensional solving + *J* + Jacobian matrix of *f* (used by multidimensional solvers) + *norm* + used vector norm (used by multidimensional solvers) + + solver has to be callable with ``(f, x0, **kwargs)`` and return an generator + yielding pairs of approximative solution and estimated error (which is + expected to be positive). + You can use the following string aliases: + 'secant', 'mnewton', 'halley', 'muller', 'illinois', 'pegasus', 'anderson', + 'ridder', 'anewton', 'bisect' + + See mpmath.calculus.optimization for their documentation. + + **Examples** + + The function :func:`~mpmath.findroot` locates a root of a given function using the + secant method by default. A simple example use of the secant method is to + compute `\pi` as the root of `\sin x` closest to `x_0 = 3`:: + + >>> from mpmath import * + >>> mp.dps = 30; mp.pretty = True + >>> findroot(sin, 3) + 3.14159265358979323846264338328 + + The secant method can be used to find complex roots of analytic functions, + although it must in that case generally be given a nonreal starting value + (or else it will never leave the real line):: + + >>> mp.dps = 15 + >>> findroot(lambda x: x**3 + 2*x + 1, j) + (0.226698825758202 + 1.46771150871022j) + + A nice application is to compute nontrivial roots of the Riemann zeta + function with many digits (good initial values are needed for convergence):: + + >>> mp.dps = 30 + >>> findroot(zeta, 0.5+14j) + (0.5 + 14.1347251417346937904572519836j) + + The secant method can also be used as an optimization algorithm, by passing + it a derivative of a function. The following example locates the positive + minimum of the gamma function:: + + >>> mp.dps = 20 + >>> findroot(lambda x: diff(gamma, x), 1) + 1.4616321449683623413 + + Finally, a useful application is to compute inverse functions, such as the + Lambert W function which is the inverse of `w e^w`, given the first + term of the solution's asymptotic expansion as the initial value. In basic + cases, this gives identical results to mpmath's built-in ``lambertw`` + function:: + + >>> def lambert(x): + ... return findroot(lambda w: w*exp(w) - x, log(1+x)) + ... + >>> mp.dps = 15 + >>> lambert(1); lambertw(1) + 0.567143290409784 + 0.567143290409784 + >>> lambert(1000); lambert(1000) + 5.2496028524016 + 5.2496028524016 + + Multidimensional functions are also supported:: + + >>> f = [lambda x1, x2: x1**2 + x2, + ... lambda x1, x2: 5*x1**2 - 3*x1 + 2*x2 - 3] + >>> findroot(f, (0, 0)) + [-0.618033988749895] + [-0.381966011250105] + >>> findroot(f, (10, 10)) + [ 1.61803398874989] + [-2.61803398874989] + + You can verify this by solving the system manually. + + Please note that the following (more general) syntax also works:: + + >>> def f(x1, x2): + ... return x1**2 + x2, 5*x1**2 - 3*x1 + 2*x2 - 3 + ... + >>> findroot(f, (0, 0)) + [-0.618033988749895] + [-0.381966011250105] + + + **Multiple roots** + + For multiple roots all methods of the Newtonian family (including secant) + converge slowly. Consider this example:: + + >>> f = lambda x: (x - 1)**99 + >>> findroot(f, 0.9, verify=False) + 0.918073542444929 + + Even for a very close starting point the secant method converges very + slowly. Use ``verbose=True`` to illustrate this. + + It is possible to modify Newton's method to make it converge regardless of + the root's multiplicity:: + + >>> findroot(f, -10, solver='mnewton') + 1.0 + + This variant uses the first and second derivative of the function, which is + not very efficient. + + Alternatively you can use an experimental Newtonian solver that keeps track + of the speed of convergence and accelerates it using Steffensen's method if + necessary:: + + >>> findroot(f, -10, solver='anewton', verbose=True) + x: -9.88888888888888888889 + error: 0.111111111111111111111 + converging slowly + x: -9.77890011223344556678 + error: 0.10998877665544332211 + converging slowly + x: -9.67002233332199662166 + error: 0.108877778911448945119 + converging slowly + accelerating convergence + x: -9.5622443299551077669 + error: 0.107778003366888854764 + converging slowly + x: 0.99999999999999999214 + error: 10.562244329955107759 + x: 1.0 + error: 7.8598304758094664213e-18 + ZeroDivisionError: canceled with x = 1.0 + 1.0 + + **Complex roots** + + For complex roots it's recommended to use Muller's method as it converges + even for real starting points very fast:: + + >>> findroot(lambda x: x**4 + x + 1, (0, 1, 2), solver='muller') + (0.727136084491197 + 0.934099289460529j) + + + **Intersection methods** + + When you need to find a root in a known interval, it's highly recommended to + use an intersection-based solver like ``'anderson'`` or ``'ridder'``. + Usually they converge faster and more reliable. They have however problems + with multiple roots and usually need a sign change to find a root:: + + >>> findroot(lambda x: x**3, (-1, 1), solver='anderson') + 0.0 + + Be careful with symmetric functions:: + + >>> findroot(lambda x: x**2, (-1, 1), solver='anderson') #doctest:+ELLIPSIS + Traceback (most recent call last): + ... + ZeroDivisionError + + It fails even for better starting points, because there is no sign change:: + + >>> findroot(lambda x: x**2, (-1, .5), solver='anderson') + Traceback (most recent call last): + ... + ValueError: Could not find root within given tolerance. (1.0 > 2.16840434497100886801e-19) + Try another starting point or tweak arguments. + + """ + prec = ctx.prec + try: + ctx.prec += 20 + + # initialize arguments + if tol is None: + tol = ctx.eps * 2**10 + + kwargs['verbose'] = kwargs.get('verbose', verbose) + + if 'd1f' in kwargs: + kwargs['df'] = kwargs['d1f'] + + kwargs['tol'] = tol + if isinstance(x0, (list, tuple)): + x0 = [ctx.convert(x) for x in x0] + else: + x0 = [ctx.convert(x0)] + + if isinstance(solver, str): + try: + solver = str2solver[solver] + except KeyError: + raise ValueError('could not recognize solver') + + # accept list of functions + if isinstance(f, (list, tuple)): + f2 = copy(f) + def tmp(*args): + return [fn(*args) for fn in f2] + f = tmp + + # detect multidimensional functions + try: + fx = f(*x0) + multidimensional = isinstance(fx, (list, tuple, ctx.matrix)) + except TypeError: + fx = f(x0[0]) + multidimensional = False + if 'multidimensional' in kwargs: + multidimensional = kwargs['multidimensional'] + if multidimensional: + # only one multidimensional solver available at the moment + solver = MDNewton + if not 'norm' in kwargs: + norm = lambda x: ctx.norm(x, 'inf') + kwargs['norm'] = norm + else: + norm = kwargs['norm'] + else: + norm = abs + + # happily return starting point if it's a root + if norm(fx) == 0: + if multidimensional: + return ctx.matrix(x0) + else: + return x0[0] + + # use solver + iterations = solver(ctx, f, x0, **kwargs) + if 'maxsteps' in kwargs: + maxsteps = kwargs['maxsteps'] + else: + maxsteps = iterations.maxsteps + i = 0 + for x, error in iterations: + if verbose: + print('x: ', x) + print('error:', error) + i += 1 + if error < tol * max(1, norm(x)) or i >= maxsteps: + break + else: + if not i: + raise ValueError('Could not find root using the given solver.\n' + 'Try another starting point or tweak arguments.') + if not isinstance(x, (list, tuple, ctx.matrix)): + xl = [x] + else: + xl = x + if verify and norm(f(*xl))**2 > tol: # TODO: better condition? + raise ValueError('Could not find root within given tolerance. ' + '(%s > %s)\n' + 'Try another starting point or tweak arguments.' + % (norm(f(*xl))**2, tol)) + return x + finally: + ctx.prec = prec + + +def multiplicity(ctx, f, root, tol=None, maxsteps=10, **kwargs): + """ + Return the multiplicity of a given root of f. + + Internally, numerical derivatives are used. This might be inefficient for + higher order derviatives. Due to this, ``multiplicity`` cancels after + evaluating 10 derivatives by default. You can be specify the n-th derivative + using the dnf keyword. + + >>> from mpmath import * + >>> multiplicity(lambda x: sin(x) - 1, pi/2) + 2 + + """ + if tol is None: + tol = ctx.eps ** 0.8 + kwargs['d0f'] = f + for i in xrange(maxsteps): + dfstr = 'd' + str(i) + 'f' + if dfstr in kwargs: + df = kwargs[dfstr] + else: + df = lambda x: ctx.diff(f, x, i) + if not abs(df(root)) < tol: + break + return i + +def steffensen(f): + """ + linear convergent function -> quadratic convergent function + + Steffensen's method for quadratic convergence of a linear converging + sequence. + Don not use it for higher rates of convergence. + It may even work for divergent sequences. + + Definition: + F(x) = (x*f(f(x)) - f(x)**2) / (f(f(x)) - 2*f(x) + x) + + Example + ....... + + You can use Steffensen's method to accelerate a fixpoint iteration of linear + (or less) convergence. + + x* is a fixpoint of the iteration x_{k+1} = phi(x_k) if x* = phi(x*). For + phi(x) = x**2 there are two fixpoints: 0 and 1. + + Let's try Steffensen's method: + + >>> f = lambda x: x**2 + >>> from mpmath.calculus.optimization import steffensen + >>> F = steffensen(f) + >>> for x in [0.5, 0.9, 2.0]: + ... fx = Fx = x + ... for i in xrange(9): + ... try: + ... fx = f(fx) + ... except OverflowError: + ... pass + ... try: + ... Fx = F(Fx) + ... except ZeroDivisionError: + ... pass + ... print('%20g %20g' % (fx, Fx)) + 0.25 -0.5 + 0.0625 0.1 + 0.00390625 -0.0011236 + 1.52588e-05 1.41691e-09 + 2.32831e-10 -2.84465e-27 + 5.42101e-20 2.30189e-80 + 2.93874e-39 -1.2197e-239 + 8.63617e-78 0 + 7.45834e-155 0 + 0.81 1.02676 + 0.6561 1.00134 + 0.430467 1 + 0.185302 1 + 0.0343368 1 + 0.00117902 1 + 1.39008e-06 1 + 1.93233e-12 1 + 3.73392e-24 1 + 4 1.6 + 16 1.2962 + 256 1.10194 + 65536 1.01659 + 4.29497e+09 1.00053 + 1.84467e+19 1 + 3.40282e+38 1 + 1.15792e+77 1 + 1.34078e+154 1 + + Unmodified, the iteration converges only towards 0. Modified it converges + not only much faster, it converges even to the repelling fixpoint 1. + """ + def F(x): + fx = f(x) + ffx = f(fx) + return (x*ffx - fx**2) / (ffx - 2*fx + x) + return F + +OptimizationMethods.jacobian = jacobian +OptimizationMethods.findroot = findroot +OptimizationMethods.multiplicity = multiplicity + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/polynomials.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/polynomials.py new file mode 100644 index 0000000000000000000000000000000000000000..ba75c1e88cbc5d40aa590a786c0af5229f193103 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/polynomials.py @@ -0,0 +1,213 @@ +from ..libmp.backend import xrange +from .calculus import defun + +#----------------------------------------------------------------------------# +# Polynomials # +#----------------------------------------------------------------------------# + +# XXX: extra precision +@defun +def polyval(ctx, coeffs, x, derivative=False): + r""" + Given coefficients `[c_n, \ldots, c_2, c_1, c_0]` and a number `x`, + :func:`~mpmath.polyval` evaluates the polynomial + + .. math :: + + P(x) = c_n x^n + \ldots + c_2 x^2 + c_1 x + c_0. + + If *derivative=True* is set, :func:`~mpmath.polyval` simultaneously + evaluates `P(x)` with the derivative, `P'(x)`, and returns the + tuple `(P(x), P'(x))`. + + >>> from mpmath import * + >>> mp.pretty = True + >>> polyval([3, 0, 2], 0.5) + 2.75 + >>> polyval([3, 0, 2], 0.5, derivative=True) + (2.75, 3.0) + + The coefficients and the evaluation point may be any combination + of real or complex numbers. + """ + if not coeffs: + return ctx.zero + p = ctx.convert(coeffs[0]) + q = ctx.zero + for c in coeffs[1:]: + if derivative: + q = p + x*q + p = c + x*p + if derivative: + return p, q + else: + return p + +@defun +def polyroots(ctx, coeffs, maxsteps=50, cleanup=True, extraprec=10, + error=False, roots_init=None): + """ + Computes all roots (real or complex) of a given polynomial. + + The roots are returned as a sorted list, where real roots appear first + followed by complex conjugate roots as adjacent elements. The polynomial + should be given as a list of coefficients, in the format used by + :func:`~mpmath.polyval`. The leading coefficient must be nonzero. + + With *error=True*, :func:`~mpmath.polyroots` returns a tuple *(roots, err)* + where *err* is an estimate of the maximum error among the computed roots. + + **Examples** + + Finding the three real roots of `x^3 - x^2 - 14x + 24`:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> nprint(polyroots([1,-1,-14,24]), 4) + [-4.0, 2.0, 3.0] + + Finding the two complex conjugate roots of `4x^2 + 3x + 2`, with an + error estimate:: + + >>> roots, err = polyroots([4,3,2], error=True) + >>> for r in roots: + ... print(r) + ... + (-0.375 + 0.59947894041409j) + (-0.375 - 0.59947894041409j) + >>> + >>> err + 2.22044604925031e-16 + >>> + >>> polyval([4,3,2], roots[0]) + (2.22044604925031e-16 + 0.0j) + >>> polyval([4,3,2], roots[1]) + (2.22044604925031e-16 + 0.0j) + + The following example computes all the 5th roots of unity; that is, + the roots of `x^5 - 1`:: + + >>> mp.dps = 20 + >>> for r in polyroots([1, 0, 0, 0, 0, -1]): + ... print(r) + ... + 1.0 + (-0.8090169943749474241 + 0.58778525229247312917j) + (-0.8090169943749474241 - 0.58778525229247312917j) + (0.3090169943749474241 + 0.95105651629515357212j) + (0.3090169943749474241 - 0.95105651629515357212j) + + **Precision and conditioning** + + The roots are computed to the current working precision accuracy. If this + accuracy cannot be achieved in ``maxsteps`` steps, then a + ``NoConvergence`` exception is raised. The algorithm internally is using + the current working precision extended by ``extraprec``. If + ``NoConvergence`` was raised, that is caused either by not having enough + extra precision to achieve convergence (in which case increasing + ``extraprec`` should fix the problem) or too low ``maxsteps`` (in which + case increasing ``maxsteps`` should fix the problem), or a combination of + both. + + The user should always do a convergence study with regards to + ``extraprec`` to ensure accurate results. It is possible to get + convergence to a wrong answer with too low ``extraprec``. + + Provided there are no repeated roots, :func:`~mpmath.polyroots` can + typically compute all roots of an arbitrary polynomial to high precision:: + + >>> mp.dps = 60 + >>> for r in polyroots([1, 0, -10, 0, 1]): + ... print(r) + ... + -3.14626436994197234232913506571557044551247712918732870123249 + -0.317837245195782244725757617296174288373133378433432554879127 + 0.317837245195782244725757617296174288373133378433432554879127 + 3.14626436994197234232913506571557044551247712918732870123249 + >>> + >>> sqrt(3) + sqrt(2) + 3.14626436994197234232913506571557044551247712918732870123249 + >>> sqrt(3) - sqrt(2) + 0.317837245195782244725757617296174288373133378433432554879127 + + **Algorithm** + + :func:`~mpmath.polyroots` implements the Durand-Kerner method [1], which + uses complex arithmetic to locate all roots simultaneously. + The Durand-Kerner method can be viewed as approximately performing + simultaneous Newton iteration for all the roots. In particular, + the convergence to simple roots is quadratic, just like Newton's + method. + + Although all roots are internally calculated using complex arithmetic, any + root found to have an imaginary part smaller than the estimated numerical + error is truncated to a real number (small real parts are also chopped). + Real roots are placed first in the returned list, sorted by value. The + remaining complex roots are sorted by their real parts so that conjugate + roots end up next to each other. + + **References** + + 1. http://en.wikipedia.org/wiki/Durand-Kerner_method + + """ + if len(coeffs) <= 1: + if not coeffs or not coeffs[0]: + raise ValueError("Input to polyroots must not be the zero polynomial") + # Constant polynomial with no roots + return [] + + orig = ctx.prec + tol = +ctx.eps + with ctx.extraprec(extraprec): + deg = len(coeffs) - 1 + # Must be monic + lead = ctx.convert(coeffs[0]) + if lead == 1: + coeffs = [ctx.convert(c) for c in coeffs] + else: + coeffs = [c/lead for c in coeffs] + f = lambda x: ctx.polyval(coeffs, x) + if roots_init is None: + roots = [ctx.mpc((0.4+0.9j)**n) for n in xrange(deg)] + else: + roots = [None]*deg; + deg_init = min(deg, len(roots_init)) + roots[:deg_init] = list(roots_init[:deg_init]) + roots[deg_init:] = [ctx.mpc((0.4+0.9j)**n) for n + in xrange(deg_init,deg)] + err = [ctx.one for n in xrange(deg)] + # Durand-Kerner iteration until convergence + for step in xrange(maxsteps): + if abs(max(err)) < tol: + break + for i in xrange(deg): + p = roots[i] + x = f(p) + for j in range(deg): + if i != j: + try: + x /= (p-roots[j]) + except ZeroDivisionError: + continue + roots[i] = p - x + err[i] = abs(x) + if abs(max(err)) >= tol: + raise ctx.NoConvergence("Didn't converge in maxsteps=%d steps." \ + % maxsteps) + # Remove small real or imaginary parts + if cleanup: + for i in xrange(deg): + if abs(roots[i]) < tol: + roots[i] = ctx.zero + elif abs(ctx._im(roots[i])) < tol: + roots[i] = roots[i].real + elif abs(ctx._re(roots[i])) < tol: + roots[i] = roots[i].imag * 1j + roots.sort(key=lambda x: (abs(ctx._im(x)), ctx._re(x))) + if error: + err = max(err) + err = max(err, ctx.ldexp(1, -orig+1)) + return [+r for r in roots], +err + else: + return [+r for r in roots] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/quadrature.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/quadrature.py new file mode 100644 index 0000000000000000000000000000000000000000..0545b3ad3e6f70a44f4ec31a0c3bcfb4ffde422b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/calculus/quadrature.py @@ -0,0 +1,1115 @@ +import math + +from ..libmp.backend import xrange + +class QuadratureRule(object): + """ + Quadrature rules are implemented using this class, in order to + simplify the code and provide a common infrastructure + for tasks such as error estimation and node caching. + + You can implement a custom quadrature rule by subclassing + :class:`QuadratureRule` and implementing the appropriate + methods. The subclass can then be used by :func:`~mpmath.quad` by + passing it as the *method* argument. + + :class:`QuadratureRule` instances are supposed to be singletons. + :class:`QuadratureRule` therefore implements instance caching + in :func:`~mpmath.__new__`. + """ + + def __init__(self, ctx): + self.ctx = ctx + self.standard_cache = {} + self.transformed_cache = {} + self.interval_count = {} + + def clear(self): + """ + Delete cached node data. + """ + self.standard_cache = {} + self.transformed_cache = {} + self.interval_count = {} + + def calc_nodes(self, degree, prec, verbose=False): + r""" + Compute nodes for the standard interval `[-1, 1]`. Subclasses + should probably implement only this method, and use + :func:`~mpmath.get_nodes` method to retrieve the nodes. + """ + raise NotImplementedError + + def get_nodes(self, a, b, degree, prec, verbose=False): + """ + Return nodes for given interval, degree and precision. The + nodes are retrieved from a cache if already computed; + otherwise they are computed by calling :func:`~mpmath.calc_nodes` + and are then cached. + + Subclasses should probably not implement this method, + but just implement :func:`~mpmath.calc_nodes` for the actual + node computation. + """ + key = (a, b, degree, prec) + if key in self.transformed_cache: + return self.transformed_cache[key] + orig = self.ctx.prec + try: + self.ctx.prec = prec+20 + # Get nodes on standard interval + if (degree, prec) in self.standard_cache: + nodes = self.standard_cache[degree, prec] + else: + nodes = self.calc_nodes(degree, prec, verbose) + self.standard_cache[degree, prec] = nodes + # Transform to general interval + nodes = self.transform_nodes(nodes, a, b, verbose) + if key in self.interval_count: + self.transformed_cache[key] = nodes + else: + self.interval_count[key] = True + finally: + self.ctx.prec = orig + return nodes + + def transform_nodes(self, nodes, a, b, verbose=False): + r""" + Rescale standardized nodes (for `[-1, 1]`) to a general + interval `[a, b]`. For a finite interval, a simple linear + change of variables is used. Otherwise, the following + transformations are used: + + .. math :: + + \lbrack a, \infty \rbrack : t = \frac{1}{x} + (a-1) + + \lbrack -\infty, b \rbrack : t = (b+1) - \frac{1}{x} + + \lbrack -\infty, \infty \rbrack : t = \frac{x}{\sqrt{1-x^2}} + + """ + ctx = self.ctx + a = ctx.convert(a) + b = ctx.convert(b) + one = ctx.one + if (a, b) == (-one, one): + return nodes + half = ctx.mpf(0.5) + new_nodes = [] + if ctx.isinf(a) or ctx.isinf(b): + if (a, b) == (ctx.ninf, ctx.inf): + p05 = -half + for x, w in nodes: + x2 = x*x + px1 = one-x2 + spx1 = px1**p05 + x = x*spx1 + w *= spx1/px1 + new_nodes.append((x, w)) + elif a == ctx.ninf: + b1 = b+1 + for x, w in nodes: + u = 2/(x+one) + x = b1-u + w *= half*u**2 + new_nodes.append((x, w)) + elif b == ctx.inf: + a1 = a-1 + for x, w in nodes: + u = 2/(x+one) + x = a1+u + w *= half*u**2 + new_nodes.append((x, w)) + elif a == ctx.inf or b == ctx.ninf: + return [(x,-w) for (x,w) in self.transform_nodes(nodes, b, a, verbose)] + else: + raise NotImplementedError + else: + # Simple linear change of variables + C = (b-a)/2 + D = (b+a)/2 + for x, w in nodes: + new_nodes.append((D+C*x, C*w)) + return new_nodes + + def guess_degree(self, prec): + """ + Given a desired precision `p` in bits, estimate the degree `m` + of the quadrature required to accomplish full accuracy for + typical integrals. By default, :func:`~mpmath.quad` will perform up + to `m` iterations. The value of `m` should be a slight + overestimate, so that "slightly bad" integrals can be dealt + with automatically using a few extra iterations. On the + other hand, it should not be too big, so :func:`~mpmath.quad` can + quit within a reasonable amount of time when it is given + an "unsolvable" integral. + + The default formula used by :func:`~mpmath.guess_degree` is tuned + for both :class:`TanhSinh` and :class:`GaussLegendre`. + The output is roughly as follows: + + +---------+---------+ + | `p` | `m` | + +=========+=========+ + | 50 | 6 | + +---------+---------+ + | 100 | 7 | + +---------+---------+ + | 500 | 10 | + +---------+---------+ + | 3000 | 12 | + +---------+---------+ + + This formula is based purely on a limited amount of + experimentation and will sometimes be wrong. + """ + # Expected degree + # XXX: use mag + g = int(4 + max(0, self.ctx.log(prec/30.0, 2))) + # Reasonable "worst case" + g += 2 + return g + + def estimate_error(self, results, prec, epsilon): + r""" + Given results from integrations `[I_1, I_2, \ldots, I_k]` done + with a quadrature of rule of degree `1, 2, \ldots, k`, estimate + the error of `I_k`. + + For `k = 2`, we estimate `|I_{\infty}-I_2|` as `|I_2-I_1|`. + + For `k > 2`, we extrapolate `|I_{\infty}-I_k| \approx |I_{k+1}-I_k|` + from `|I_k-I_{k-1}|` and `|I_k-I_{k-2}|` under the assumption + that each degree increment roughly doubles the accuracy of + the quadrature rule (this is true for both :class:`TanhSinh` + and :class:`GaussLegendre`). The extrapolation formula is given + by Borwein, Bailey & Girgensohn. Although not very conservative, + this method seems to be very robust in practice. + """ + if len(results) == 2: + return abs(results[0]-results[1]) + try: + if results[-1] == results[-2] == results[-3]: + return self.ctx.zero + D1 = self.ctx.log(abs(results[-1]-results[-2]), 10) + D2 = self.ctx.log(abs(results[-1]-results[-3]), 10) + except ValueError: + return epsilon + D3 = -prec + D4 = min(0, max(D1**2/D2, 2*D1, D3)) + return self.ctx.mpf(10) ** int(D4) + + def summation(self, f, points, prec, epsilon, max_degree, verbose=False): + """ + Main integration function. Computes the 1D integral over + the interval specified by *points*. For each subinterval, + performs quadrature of degree from 1 up to *max_degree* + until :func:`~mpmath.estimate_error` signals convergence. + + :func:`~mpmath.summation` transforms each subintegration to + the standard interval and then calls :func:`~mpmath.sum_next`. + """ + ctx = self.ctx + I = total_err = ctx.zero + for i in xrange(len(points)-1): + a, b = points[i], points[i+1] + if a == b: + continue + # XXX: we could use a single variable transformation, + # but this is not good in practice. We get better accuracy + # by having 0 as an endpoint. + if (a, b) == (ctx.ninf, ctx.inf): + _f = f + f = lambda x: _f(-x) + _f(x) + a, b = (ctx.zero, ctx.inf) + results = [] + err = ctx.zero + for degree in xrange(1, max_degree+1): + nodes = self.get_nodes(a, b, degree, prec, verbose) + if verbose: + print("Integrating from %s to %s (degree %s of %s)" % \ + (ctx.nstr(a), ctx.nstr(b), degree, max_degree)) + result = self.sum_next(f, nodes, degree, prec, results, verbose) + results.append(result) + if degree > 1: + err = self.estimate_error(results, prec, epsilon) + if verbose: + print("Estimated error:", ctx.nstr(err), " epsilon:", ctx.nstr(epsilon), " result: ", ctx.nstr(result)) + if err <= epsilon: + break + I += results[-1] + total_err += err + if total_err > epsilon: + if verbose: + print("Failed to reach full accuracy. Estimated error:", ctx.nstr(total_err)) + return I, total_err + + def sum_next(self, f, nodes, degree, prec, previous, verbose=False): + r""" + Evaluates the step sum `\sum w_k f(x_k)` where the *nodes* list + contains the `(w_k, x_k)` pairs. + + :func:`~mpmath.summation` will supply the list *results* of + values computed by :func:`~mpmath.sum_next` at previous degrees, in + case the quadrature rule is able to reuse them. + """ + return self.ctx.fdot((w, f(x)) for (x,w) in nodes) + + +class TanhSinh(QuadratureRule): + r""" + This class implements "tanh-sinh" or "doubly exponential" + quadrature. This quadrature rule is based on the Euler-Maclaurin + integral formula. By performing a change of variables involving + nested exponentials / hyperbolic functions (hence the name), the + derivatives at the endpoints vanish rapidly. Since the error term + in the Euler-Maclaurin formula depends on the derivatives at the + endpoints, a simple step sum becomes extremely accurate. In + practice, this means that doubling the number of evaluation + points roughly doubles the number of accurate digits. + + Comparison to Gauss-Legendre: + * Initial computation of nodes is usually faster + * Handles endpoint singularities better + * Handles infinite integration intervals better + * Is slower for smooth integrands once nodes have been computed + + The implementation of the tanh-sinh algorithm is based on the + description given in Borwein, Bailey & Girgensohn, "Experimentation + in Mathematics - Computational Paths to Discovery", A K Peters, + 2003, pages 312-313. In the present implementation, a few + improvements have been made: + + * A more efficient scheme is used to compute nodes (exploiting + recurrence for the exponential function) + * The nodes are computed successively instead of all at once + + **References** + + * [Bailey]_ + * http://users.cs.dal.ca/~jborwein/tanh-sinh.pdf + + """ + + def sum_next(self, f, nodes, degree, prec, previous, verbose=False): + """ + Step sum for tanh-sinh quadrature of degree `m`. We exploit the + fact that half of the abscissas at degree `m` are precisely the + abscissas from degree `m-1`. Thus reusing the result from + the previous level allows a 2x speedup. + """ + h = self.ctx.mpf(2)**(-degree) + # Abscissas overlap, so reusing saves half of the time + if previous: + S = previous[-1]/(h*2) + else: + S = self.ctx.zero + S += self.ctx.fdot((w,f(x)) for (x,w) in nodes) + return h*S + + def calc_nodes(self, degree, prec, verbose=False): + r""" + The abscissas and weights for tanh-sinh quadrature of degree + `m` are given by + + .. math:: + + x_k = \tanh(\pi/2 \sinh(t_k)) + + w_k = \pi/2 \cosh(t_k) / \cosh(\pi/2 \sinh(t_k))^2 + + where `t_k = t_0 + hk` for a step length `h \sim 2^{-m}`. The + list of nodes is actually infinite, but the weights die off so + rapidly that only a few are needed. + """ + ctx = self.ctx + nodes = [] + + extra = 20 + ctx.prec += extra + tol = ctx.ldexp(1, -prec-10) + pi4 = ctx.pi/4 + + # For simplicity, we work in steps h = 1/2^n, with the first point + # offset so that we can reuse the sum from the previous degree + + # We define degree 1 to include the "degree 0" steps, including + # the point x = 0. (It doesn't work well otherwise; not sure why.) + t0 = ctx.ldexp(1, -degree) + if degree == 1: + #nodes.append((mpf(0), pi4)) + #nodes.append((-mpf(0), pi4)) + nodes.append((ctx.zero, ctx.pi/2)) + h = t0 + else: + h = t0*2 + + # Since h is fixed, we can compute the next exponential + # by simply multiplying by exp(h) + expt0 = ctx.exp(t0) + a = pi4 * expt0 + b = pi4 / expt0 + udelta = ctx.exp(h) + urdelta = 1/udelta + + for k in xrange(0, 20*2**degree+1): + # Reference implementation: + # t = t0 + k*h + # x = tanh(pi/2 * sinh(t)) + # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 + + # Fast implementation. Note that c = exp(pi/2 * sinh(t)) + c = ctx.exp(a-b) + d = 1/c + co = (c+d)/2 + si = (c-d)/2 + x = si / co + w = (a+b) / co**2 + diff = abs(x-1) + if diff <= tol: + break + + nodes.append((x, w)) + nodes.append((-x, w)) + + a *= udelta + b *= urdelta + + if verbose and k % 300 == 150: + # Note: the number displayed is rather arbitrary. Should + # figure out how to print something that looks more like a + # percentage + print("Calculating nodes:", ctx.nstr(-ctx.log(diff, 10) / prec)) + + ctx.prec -= extra + return nodes + + +class GaussLegendre(QuadratureRule): + r""" + This class implements Gauss-Legendre quadrature, which is + exceptionally efficient for polynomials and polynomial-like (i.e. + very smooth) integrands. + + The abscissas and weights are given by roots and values of + Legendre polynomials, which are the orthogonal polynomials + on `[-1, 1]` with respect to the unit weight + (see :func:`~mpmath.legendre`). + + In this implementation, we take the "degree" `m` of the quadrature + to denote a Gauss-Legendre rule of degree `3 \cdot 2^m` (following + Borwein, Bailey & Girgensohn). This way we get quadratic, rather + than linear, convergence as the degree is incremented. + + Comparison to tanh-sinh quadrature: + * Is faster for smooth integrands once nodes have been computed + * Initial computation of nodes is usually slower + * Handles endpoint singularities worse + * Handles infinite integration intervals worse + + """ + + def calc_nodes(self, degree, prec, verbose=False): + r""" + Calculates the abscissas and weights for Gauss-Legendre + quadrature of degree of given degree (actually `3 \cdot 2^m`). + """ + ctx = self.ctx + # It is important that the epsilon is set lower than the + # "real" epsilon + epsilon = ctx.ldexp(1, -prec-8) + # Fairly high precision might be required for accurate + # evaluation of the roots + orig = ctx.prec + ctx.prec = int(prec*1.5) + if degree == 1: + x = ctx.sqrt(ctx.mpf(3)/5) + w = ctx.mpf(5)/9 + nodes = [(-x,w),(ctx.zero,ctx.mpf(8)/9),(x,w)] + ctx.prec = orig + return nodes + nodes = [] + n = 3*2**(degree-1) + upto = n//2 + 1 + for j in xrange(1, upto): + # Asymptotic formula for the roots + r = ctx.mpf(math.cos(math.pi*(j-0.25)/(n+0.5))) + # Newton iteration + while 1: + t1, t2 = 1, 0 + # Evaluates the Legendre polynomial using its defining + # recurrence relation + for j1 in xrange(1,n+1): + t3, t2, t1 = t2, t1, ((2*j1-1)*r*t1 - (j1-1)*t2)/j1 + t4 = n*(r*t1-t2)/(r**2-1) + a = t1/t4 + r = r - a + if abs(a) < epsilon: + break + x = r + w = 2/((1-r**2)*t4**2) + if verbose and j % 30 == 15: + print("Computing nodes (%i of %i)" % (j, upto)) + nodes.append((x, w)) + nodes.append((-x, w)) + ctx.prec = orig + return nodes + +class QuadratureMethods(object): + + def __init__(ctx, *args, **kwargs): + ctx._gauss_legendre = GaussLegendre(ctx) + ctx._tanh_sinh = TanhSinh(ctx) + + def quad(ctx, f, *points, **kwargs): + r""" + Computes a single, double or triple integral over a given + 1D interval, 2D rectangle, or 3D cuboid. A basic example:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> quad(sin, [0, pi]) + 2.0 + + A basic 2D integral:: + + >>> f = lambda x, y: cos(x+y/2) + >>> quad(f, [-pi/2, pi/2], [0, pi]) + 4.0 + + **Interval format** + + The integration range for each dimension may be specified + using a list or tuple. Arguments are interpreted as follows: + + ``quad(f, [x1, x2])`` -- calculates + `\int_{x_1}^{x_2} f(x) \, dx` + + ``quad(f, [x1, x2], [y1, y2])`` -- calculates + `\int_{x_1}^{x_2} \int_{y_1}^{y_2} f(x,y) \, dy \, dx` + + ``quad(f, [x1, x2], [y1, y2], [z1, z2])`` -- calculates + `\int_{x_1}^{x_2} \int_{y_1}^{y_2} \int_{z_1}^{z_2} f(x,y,z) + \, dz \, dy \, dx` + + Endpoints may be finite or infinite. An interval descriptor + may also contain more than two points. In this + case, the integration is split into subintervals, between + each pair of consecutive points. This is useful for + dealing with mid-interval discontinuities, or integrating + over large intervals where the function is irregular or + oscillates. + + **Options** + + :func:`~mpmath.quad` recognizes the following keyword arguments: + + *method* + Chooses integration algorithm (described below). + *error* + If set to true, :func:`~mpmath.quad` returns `(v, e)` where `v` is the + integral and `e` is the estimated error. + *maxdegree* + Maximum degree of the quadrature rule to try before + quitting. + *verbose* + Print details about progress. + + **Algorithms** + + Mpmath presently implements two integration algorithms: tanh-sinh + quadrature and Gauss-Legendre quadrature. These can be selected + using *method='tanh-sinh'* or *method='gauss-legendre'* or by + passing the classes *method=TanhSinh*, *method=GaussLegendre*. + The functions :func:`~mpmath.quadts` and :func:`~mpmath.quadgl` are also available + as shortcuts. + + Both algorithms have the property that doubling the number of + evaluation points roughly doubles the accuracy, so both are ideal + for high precision quadrature (hundreds or thousands of digits). + + At high precision, computing the nodes and weights for the + integration can be expensive (more expensive than computing the + function values). To make repeated integrations fast, nodes + are automatically cached. + + The advantages of the tanh-sinh algorithm are that it tends to + handle endpoint singularities well, and that the nodes are cheap + to compute on the first run. For these reasons, it is used by + :func:`~mpmath.quad` as the default algorithm. + + Gauss-Legendre quadrature often requires fewer function + evaluations, and is therefore often faster for repeated use, but + the algorithm does not handle endpoint singularities as well and + the nodes are more expensive to compute. Gauss-Legendre quadrature + can be a better choice if the integrand is smooth and repeated + integrations are required (e.g. for multiple integrals). + + See the documentation for :class:`TanhSinh` and + :class:`GaussLegendre` for additional details. + + **Examples of 1D integrals** + + Intervals may be infinite or half-infinite. The following two + examples evaluate the limits of the inverse tangent function + (`\int 1/(1+x^2) = \tan^{-1} x`), and the Gaussian integral + `\int_{\infty}^{\infty} \exp(-x^2)\,dx = \sqrt{\pi}`:: + + >>> mp.dps = 15 + >>> quad(lambda x: 2/(x**2+1), [0, inf]) + 3.14159265358979 + >>> quad(lambda x: exp(-x**2), [-inf, inf])**2 + 3.14159265358979 + + Integrals can typically be resolved to high precision. + The following computes 50 digits of `\pi` by integrating the + area of the half-circle defined by `x^2 + y^2 \le 1`, + `-1 \le x \le 1`, `y \ge 0`:: + + >>> mp.dps = 50 + >>> 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) + 3.1415926535897932384626433832795028841971693993751 + + One can just as well compute 1000 digits (output truncated):: + + >>> mp.dps = 1000 + >>> 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) #doctest:+ELLIPSIS + 3.141592653589793238462643383279502884...216420199 + + Complex integrals are supported. The following computes + a residue at `z = 0` by integrating counterclockwise along the + diamond-shaped path from `1` to `+i` to `-1` to `-i` to `1`:: + + >>> mp.dps = 15 + >>> chop(quad(lambda z: 1/z, [1,j,-1,-j,1])) + (0.0 + 6.28318530717959j) + + **Examples of 2D and 3D integrals** + + Here are several nice examples of analytically solvable + 2D integrals (taken from MathWorld [1]) that can be evaluated + to high precision fairly rapidly by :func:`~mpmath.quad`:: + + >>> mp.dps = 30 + >>> f = lambda x, y: (x-1)/((1-x*y)*log(x*y)) + >>> quad(f, [0, 1], [0, 1]) + 0.577215664901532860606512090082 + >>> +euler + 0.577215664901532860606512090082 + + >>> f = lambda x, y: 1/sqrt(1+x**2+y**2) + >>> quad(f, [-1, 1], [-1, 1]) + 3.17343648530607134219175646705 + >>> 4*log(2+sqrt(3))-2*pi/3 + 3.17343648530607134219175646705 + + >>> f = lambda x, y: 1/(1-x**2 * y**2) + >>> quad(f, [0, 1], [0, 1]) + 1.23370055013616982735431137498 + >>> pi**2 / 8 + 1.23370055013616982735431137498 + + >>> quad(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]) + 1.64493406684822643647241516665 + >>> pi**2 / 6 + 1.64493406684822643647241516665 + + Multiple integrals may be done over infinite ranges:: + + >>> mp.dps = 15 + >>> print(quad(lambda x,y: exp(-x-y), [0, inf], [1, inf])) + 0.367879441171442 + >>> print(1/e) + 0.367879441171442 + + For nonrectangular areas, one can call :func:`~mpmath.quad` recursively. + For example, we can replicate the earlier example of calculating + `\pi` by integrating over the unit-circle, and actually use double + quadrature to actually measure the area circle:: + + >>> f = lambda x: quad(lambda y: 1, [-sqrt(1-x**2), sqrt(1-x**2)]) + >>> quad(f, [-1, 1]) + 3.14159265358979 + + Here is a simple triple integral:: + + >>> mp.dps = 15 + >>> f = lambda x,y,z: x*y/(1+z) + >>> quad(f, [0,1], [0,1], [1,2], method='gauss-legendre') + 0.101366277027041 + >>> (log(3)-log(2))/4 + 0.101366277027041 + + **Singularities** + + Both tanh-sinh and Gauss-Legendre quadrature are designed to + integrate smooth (infinitely differentiable) functions. Neither + algorithm copes well with mid-interval singularities (such as + mid-interval discontinuities in `f(x)` or `f'(x)`). + The best solution is to split the integral into parts:: + + >>> mp.dps = 15 + >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) # Bad + 3.99900894176779 + >>> quad(lambda x: abs(sin(x)), [0, pi, 2*pi]) # Good + 4.0 + + The tanh-sinh rule often works well for integrands having a + singularity at one or both endpoints:: + + >>> mp.dps = 15 + >>> quad(log, [0, 1], method='tanh-sinh') # Good + -1.0 + >>> quad(log, [0, 1], method='gauss-legendre') # Bad + -0.999932197413801 + + However, the result may still be inaccurate for some functions:: + + >>> quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') + 1.99999999946942 + + This problem is not due to the quadrature rule per se, but to + numerical amplification of errors in the nodes. The problem can be + circumvented by temporarily increasing the precision:: + + >>> mp.dps = 30 + >>> a = quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') + >>> mp.dps = 15 + >>> +a + 2.0 + + **Highly variable functions** + + For functions that are smooth (in the sense of being infinitely + differentiable) but contain sharp mid-interval peaks or many + "bumps", :func:`~mpmath.quad` may fail to provide full accuracy. For + example, with default settings, :func:`~mpmath.quad` is able to integrate + `\sin(x)` accurately over an interval of length 100 but not over + length 1000:: + + >>> quad(sin, [0, 100]); 1-cos(100) # Good + 0.137681127712316 + 0.137681127712316 + >>> quad(sin, [0, 1000]); 1-cos(1000) # Bad + -37.8587612408485 + 0.437620923709297 + + One solution is to break the integration into 10 intervals of + length 100:: + + >>> quad(sin, linspace(0, 1000, 10)) # Good + 0.437620923709297 + + Another is to increase the degree of the quadrature:: + + >>> quad(sin, [0, 1000], maxdegree=10) # Also good + 0.437620923709297 + + Whether splitting the interval or increasing the degree is + more efficient differs from case to case. Another example is the + function `1/(1+x^2)`, which has a sharp peak centered around + `x = 0`:: + + >>> f = lambda x: 1/(1+x**2) + >>> quad(f, [-100, 100]) # Bad + 3.64804647105268 + >>> quad(f, [-100, 100], maxdegree=10) # Good + 3.12159332021646 + >>> quad(f, [-100, 0, 100]) # Also good + 3.12159332021646 + + **References** + + 1. http://mathworld.wolfram.com/DoubleIntegral.html + + """ + rule = kwargs.get('method', 'tanh-sinh') + if type(rule) is str: + if rule == 'tanh-sinh': + rule = ctx._tanh_sinh + elif rule == 'gauss-legendre': + rule = ctx._gauss_legendre + else: + raise ValueError("unknown quadrature rule: %s" % rule) + else: + rule = rule(ctx) + verbose = kwargs.get('verbose') + dim = len(points) + orig = prec = ctx.prec + epsilon = ctx.eps/8 + m = kwargs.get('maxdegree') or rule.guess_degree(prec) + points = [ctx._as_points(p) for p in points] + try: + ctx.prec += 20 + if dim == 1: + v, err = rule.summation(f, points[0], prec, epsilon, m, verbose) + elif dim == 2: + v, err = rule.summation(lambda x: \ + rule.summation(lambda y: f(x,y), \ + points[1], prec, epsilon, m)[0], + points[0], prec, epsilon, m, verbose) + elif dim == 3: + v, err = rule.summation(lambda x: \ + rule.summation(lambda y: \ + rule.summation(lambda z: f(x,y,z), \ + points[2], prec, epsilon, m)[0], + points[1], prec, epsilon, m)[0], + points[0], prec, epsilon, m, verbose) + else: + raise NotImplementedError("quadrature must have dim 1, 2 or 3") + finally: + ctx.prec = orig + if kwargs.get("error"): + return +v, err + return +v + + def quadts(ctx, *args, **kwargs): + """ + Performs tanh-sinh quadrature. The call + + quadts(func, *points, ...) + + is simply a shortcut for: + + quad(func, *points, ..., method=TanhSinh) + + For example, a single integral and a double integral: + + quadts(lambda x: exp(cos(x)), [0, 1]) + quadts(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) + + See the documentation for quad for information about how points + arguments and keyword arguments are parsed. + + See documentation for TanhSinh for algorithmic information about + tanh-sinh quadrature. + """ + kwargs['method'] = 'tanh-sinh' + return ctx.quad(*args, **kwargs) + + def quadgl(ctx, *args, **kwargs): + """ + Performs Gauss-Legendre quadrature. The call + + quadgl(func, *points, ...) + + is simply a shortcut for: + + quad(func, *points, ..., method=GaussLegendre) + + For example, a single integral and a double integral: + + quadgl(lambda x: exp(cos(x)), [0, 1]) + quadgl(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) + + See the documentation for quad for information about how points + arguments and keyword arguments are parsed. + + See documentation for TanhSinh for algorithmic information about + tanh-sinh quadrature. + """ + kwargs['method'] = 'gauss-legendre' + return ctx.quad(*args, **kwargs) + + def quadosc(ctx, f, interval, omega=None, period=None, zeros=None): + r""" + Calculates + + .. math :: + + I = \int_a^b f(x) dx + + where at least one of `a` and `b` is infinite and where + `f(x) = g(x) \cos(\omega x + \phi)` for some slowly + decreasing function `g(x)`. With proper input, :func:`~mpmath.quadosc` + can also handle oscillatory integrals where the oscillation + rate is different from a pure sine or cosine wave. + + In the standard case when `|a| < \infty, b = \infty`, + :func:`~mpmath.quadosc` works by evaluating the infinite series + + .. math :: + + I = \int_a^{x_1} f(x) dx + + \sum_{k=1}^{\infty} \int_{x_k}^{x_{k+1}} f(x) dx + + where `x_k` are consecutive zeros (alternatively + some other periodic reference point) of `f(x)`. + Accordingly, :func:`~mpmath.quadosc` requires information about the + zeros of `f(x)`. For a periodic function, you can specify + the zeros by either providing the angular frequency `\omega` + (*omega*) or the *period* `2 \pi/\omega`. In general, you can + specify the `n`-th zero by providing the *zeros* arguments. + Below is an example of each:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> f = lambda x: sin(3*x)/(x**2+1) + >>> quadosc(f, [0,inf], omega=3) + 0.37833007080198 + >>> quadosc(f, [0,inf], period=2*pi/3) + 0.37833007080198 + >>> quadosc(f, [0,inf], zeros=lambda n: pi*n/3) + 0.37833007080198 + >>> (ei(3)*exp(-3)-exp(3)*ei(-3))/2 # Computed by Mathematica + 0.37833007080198 + + Note that *zeros* was specified to multiply `n` by the + *half-period*, not the full period. In theory, it does not matter + whether each partial integral is done over a half period or a full + period. However, if done over half-periods, the infinite series + passed to :func:`~mpmath.nsum` becomes an *alternating series* and this + typically makes the extrapolation much more efficient. + + Here is an example of an integration over the entire real line, + and a half-infinite integration starting at `-\infty`:: + + >>> quadosc(lambda x: cos(x)/(1+x**2), [-inf, inf], omega=1) + 1.15572734979092 + >>> pi/e + 1.15572734979092 + >>> quadosc(lambda x: cos(x)/x**2, [-inf, -1], period=2*pi) + -0.0844109505595739 + >>> cos(1)+si(1)-pi/2 + -0.0844109505595738 + + Of course, the integrand may contain a complex exponential just as + well as a real sine or cosine:: + + >>> quadosc(lambda x: exp(3*j*x)/(1+x**2), [-inf,inf], omega=3) + (0.156410688228254 + 0.0j) + >>> pi/e**3 + 0.156410688228254 + >>> quadosc(lambda x: exp(3*j*x)/(2+x+x**2), [-inf,inf], omega=3) + (0.00317486988463794 - 0.0447701735209082j) + >>> 2*pi/sqrt(7)/exp(3*(j+sqrt(7))/2) + (0.00317486988463794 - 0.0447701735209082j) + + **Non-periodic functions** + + If `f(x) = g(x) h(x)` for some function `h(x)` that is not + strictly periodic, *omega* or *period* might not work, and it might + be necessary to use *zeros*. + + A notable exception can be made for Bessel functions which, though not + periodic, are "asymptotically periodic" in a sufficiently strong sense + that the sum extrapolation will work out:: + + >>> quadosc(j0, [0, inf], period=2*pi) + 1.0 + >>> quadosc(j1, [0, inf], period=2*pi) + 1.0 + + More properly, one should provide the exact Bessel function zeros:: + + >>> j0zero = lambda n: findroot(j0, pi*(n-0.25)) + >>> quadosc(j0, [0, inf], zeros=j0zero) + 1.0 + + For an example where *zeros* becomes necessary, consider the + complete Fresnel integrals + + .. math :: + + \int_0^{\infty} \cos x^2\,dx = \int_0^{\infty} \sin x^2\,dx + = \sqrt{\frac{\pi}{8}}. + + Although the integrands do not decrease in magnitude as + `x \to \infty`, the integrals are convergent since the oscillation + rate increases (causing consecutive periods to asymptotically + cancel out). These integrals are virtually impossible to calculate + to any kind of accuracy using standard quadrature rules. However, + if one provides the correct asymptotic distribution of zeros + (`x_n \sim \sqrt{n}`), :func:`~mpmath.quadosc` works:: + + >>> mp.dps = 30 + >>> f = lambda x: cos(x**2) + >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) + 0.626657068657750125603941321203 + >>> f = lambda x: sin(x**2) + >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) + 0.626657068657750125603941321203 + >>> sqrt(pi/8) + 0.626657068657750125603941321203 + + (Interestingly, these integrals can still be evaluated if one + places some other constant than `\pi` in the square root sign.) + + In general, if `f(x) \sim g(x) \cos(h(x))`, the zeros follow + the inverse-function distribution `h^{-1}(x)`:: + + >>> mp.dps = 15 + >>> f = lambda x: sin(exp(x)) + >>> quadosc(f, [1,inf], zeros=lambda n: log(n)) + -0.25024394235267 + >>> pi/2-si(e) + -0.250243942352671 + + **Non-alternating functions** + + If the integrand oscillates around a positive value, without + alternating signs, the extrapolation might fail. A simple trick + that sometimes works is to multiply or divide the frequency by 2:: + + >>> f = lambda x: 1/x**2+sin(x)/x**4 + >>> quadosc(f, [1,inf], omega=1) # Bad + 1.28642190869861 + >>> quadosc(f, [1,inf], omega=0.5) # Perfect + 1.28652953559617 + >>> 1+(cos(1)+ci(1)+sin(1))/6 + 1.28652953559617 + + **Fast decay** + + :func:`~mpmath.quadosc` is primarily useful for slowly decaying + integrands. If the integrand decreases exponentially or faster, + :func:`~mpmath.quad` will likely handle it without trouble (and generally be + much faster than :func:`~mpmath.quadosc`):: + + >>> quadosc(lambda x: cos(x)/exp(x), [0, inf], omega=1) + 0.5 + >>> quad(lambda x: cos(x)/exp(x), [0, inf]) + 0.5 + + """ + a, b = ctx._as_points(interval) + a = ctx.convert(a) + b = ctx.convert(b) + if [omega, period, zeros].count(None) != 2: + raise ValueError( \ + "must specify exactly one of omega, period, zeros") + if a == ctx.ninf and b == ctx.inf: + s1 = ctx.quadosc(f, [a, 0], omega=omega, zeros=zeros, period=period) + s2 = ctx.quadosc(f, [0, b], omega=omega, zeros=zeros, period=period) + return s1 + s2 + if a == ctx.ninf: + if zeros: + return ctx.quadosc(lambda x:f(-x), [-b,-a], lambda n: zeros(-n)) + else: + return ctx.quadosc(lambda x:f(-x), [-b,-a], omega=omega, period=period) + if b != ctx.inf: + raise ValueError("quadosc requires an infinite integration interval") + if not zeros: + if omega: + period = 2*ctx.pi/omega + zeros = lambda n: n*period/2 + #for n in range(1,10): + # p = zeros(n) + # if p > a: + # break + #if n >= 9: + # raise ValueError("zeros do not appear to be correctly indexed") + n = 1 + s = ctx.quadgl(f, [a, zeros(n)]) + def term(k): + return ctx.quadgl(f, [zeros(k), zeros(k+1)]) + s += ctx.nsum(term, [n, ctx.inf]) + return s + + def quadsubdiv(ctx, f, interval, tol=None, maxintervals=None, **kwargs): + """ + Computes the integral of *f* over the interval or path specified + by *interval*, using :func:`~mpmath.quad` together with adaptive + subdivision of the interval. + + This function gives an accurate answer for some integrals where + :func:`~mpmath.quad` fails:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) + 3.99900894176779 + >>> quadsubdiv(lambda x: abs(sin(x)), [0, 2*pi]) + 4.0 + >>> quadsubdiv(sin, [0, 1000]) + 0.437620923709297 + >>> quadsubdiv(lambda x: 1/(1+x**2), [-100, 100]) + 3.12159332021646 + >>> quadsubdiv(lambda x: ceil(x), [0, 100]) + 5050.0 + >>> quadsubdiv(lambda x: sin(x+exp(x)), [0,8]) + 0.347400172657248 + + The argument *maxintervals* can be set to limit the permissible + subdivision:: + + >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=5, error=True) + (-5.40487904307774, 5.011) + >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=100, error=True) + (0.631417921866934, 1.10101120134116e-17) + + Subdivision does not guarantee a correct answer since, the error + estimate on subintervals may be inaccurate:: + + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) + (0.210802735500549, 1.0001111101e-17) + >>> mp.dps = 20 + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) + (0.21080273550054927738, 2.200000001e-24) + + The second answer is correct. We can get an accurate result at lower + precision by forcing a finer initial subdivision:: + + >>> mp.dps = 15 + >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, linspace(0,1,5)) + 0.210802735500549 + + The following integral is too oscillatory for convergence, but we can get a + reasonable estimate:: + + >>> v, err = fp.quadsubdiv(lambda x: fp.sin(1/x), [0,1], error=True) + >>> round(v, 6), round(err, 6) + (0.504067, 1e-06) + >>> sin(1) - ci(1) + 0.504067061906928 + + """ + queue = [] + for i in range(len(interval)-1): + queue.append((interval[i], interval[i+1])) + total = ctx.zero + total_error = ctx.zero + if maxintervals is None: + maxintervals = 10 * ctx.prec + count = 0 + quad_args = kwargs.copy() + quad_args["verbose"] = False + quad_args["error"] = True + if tol is None: + tol = +ctx.eps + orig = ctx.prec + try: + ctx.prec += 5 + while queue: + a, b = queue.pop() + s, err = ctx.quad(f, [a, b], **quad_args) + if kwargs.get("verbose"): + print("subinterval", count, a, b, err) + if err < tol or count > maxintervals: + total += s + total_error += err + else: + count += 1 + if count == maxintervals and kwargs.get("verbose"): + print("warning: number of intervals exceeded maxintervals") + if a == -ctx.inf and b == ctx.inf: + m = 0 + elif a == -ctx.inf: + m = min(b-1, 2*b) + elif b == ctx.inf: + m = max(a+1, 2*a) + else: + m = a + (b - a) / 2 + queue.append((a, m)) + queue.append((m, b)) + finally: + ctx.prec = orig + if kwargs.get("error"): + return +total, +total_error + else: + return +total + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5896ed0579eceab086dc5c67eaa649b6061a53dc --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__init__.py @@ -0,0 +1,14 @@ +from . import functions +# Hack to update methods +from . import factorials +from . import hypergeometric +from . import expintegrals +from . import bessel +from . import orthogonal +from . import theta +from . import elliptic +from . import signals +from . import zeta +from . import rszeta +from . import zetazeros +from . import qfunctions diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a1f09f810865c72f3eca814304fe9f1259fe81 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/bessel.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/bessel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6f61c9cc0fba20b353f034b681061d9660184f5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/bessel.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/elliptic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/elliptic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cae32dd472471f52d840e807dcfe11946616117b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/elliptic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/expintegrals.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/expintegrals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..200fd0576386b21e06e28f1daf569f58c33a48ad Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/expintegrals.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/factorials.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/factorials.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe848237cbf0f60611381c74e773c533881240d3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/factorials.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/functions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/functions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..781ae64b92b4e439e30ceb7f850d7578290d3ad7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/functions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/hypergeometric.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/hypergeometric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b69be8dd977d89bc313fb17ef93a0c239d34fe8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/hypergeometric.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/orthogonal.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/orthogonal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..283b8bef38591eee6945119869019af1cb661309 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/orthogonal.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/qfunctions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/qfunctions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cabb34410ad9cd52ef0c9aa12f1040af644fcac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/qfunctions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/rszeta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/rszeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d946fab5a17e0b8ee08221be6a03df8ffadef61 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/rszeta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/signals.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/signals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5cc371fa4d2ccf838cb479275a2b27bef098650 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/signals.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/theta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/theta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d91e88c2de2f62ebba6e48c89a8350ad1db04d6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/theta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zeta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0d84ea9c9fed86969869554769346c02132812a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zeta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zetazeros.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zetazeros.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc40297686e172fdef19a96d1f3a5732557de494 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/__pycache__/zetazeros.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/bessel.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/bessel.py new file mode 100644 index 0000000000000000000000000000000000000000..8b41d87bb0118de61d5561433dabcb181f872f84 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/bessel.py @@ -0,0 +1,1108 @@ +from .functions import defun, defun_wrapped + +@defun +def j0(ctx, x): + """Computes the Bessel function `J_0(x)`. See :func:`~mpmath.besselj`.""" + return ctx.besselj(0, x) + +@defun +def j1(ctx, x): + """Computes the Bessel function `J_1(x)`. See :func:`~mpmath.besselj`.""" + return ctx.besselj(1, x) + +@defun +def besselj(ctx, n, z, derivative=0, **kwargs): + if type(n) is int: + n_isint = True + else: + n = ctx.convert(n) + n_isint = ctx.isint(n) + if n_isint: + n = int(ctx._re(n)) + if n_isint and n < 0: + return (-1)**n * ctx.besselj(-n, z, derivative, **kwargs) + z = ctx.convert(z) + M = ctx.mag(z) + if derivative: + d = ctx.convert(derivative) + # TODO: the integer special-casing shouldn't be necessary. + # However, the hypergeometric series gets inaccurate for large d + # because of inaccurate pole cancellation at a pole far from + # zero (needs to be fixed in hypercomb or hypsum) + if ctx.isint(d) and d >= 0: + d = int(d) + orig = ctx.prec + try: + ctx.prec += 15 + v = ctx.fsum((-1)**k * ctx.binomial(d,k) * ctx.besselj(2*k+n-d,z) + for k in range(d+1)) + finally: + ctx.prec = orig + v *= ctx.mpf(2)**(-d) + else: + def h(n,d): + r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), -0.25, exact=True) + B = [0.5*(n-d+1), 0.5*(n-d+2)] + T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[],B,[(n+1)*0.5,(n+2)*0.5],B+[n+1],r)] + return T + v = ctx.hypercomb(h, [n,d], **kwargs) + else: + # Fast case: J_n(x), n int, appropriate magnitude for fixed-point calculation + if (not derivative) and n_isint and abs(M) < 10 and abs(n) < 20: + try: + return ctx._besselj(n, z) + except NotImplementedError: + pass + if not z: + if not n: + v = ctx.one + n+z + elif ctx.re(n) > 0: + v = n*z + else: + v = ctx.inf + z + n + else: + #v = 0 + orig = ctx.prec + try: + # XXX: workaround for accuracy in low level hypergeometric series + # when alternating, large arguments + ctx.prec += min(3*abs(M), ctx.prec) + w = ctx.fmul(z, 0.5, exact=True) + def h(n): + r = ctx.fneg(ctx.fmul(w, w, prec=max(0,ctx.prec+M)), exact=True) + return [([w], [n], [], [n+1], [], [n+1], r)] + v = ctx.hypercomb(h, [n], **kwargs) + finally: + ctx.prec = orig + v = +v + return v + +@defun +def besseli(ctx, n, z, derivative=0, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + if not z: + if derivative: + raise ValueError + if not n: + # I(0,0) = 1 + return 1+n+z + if ctx.isint(n): + return 0*(n+z) + r = ctx.re(n) + if r == 0: + return ctx.nan*(n+z) + elif r > 0: + return 0*(n+z) + else: + return ctx.inf+(n+z) + M = ctx.mag(z) + if derivative: + d = ctx.convert(derivative) + def h(n,d): + r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), 0.25, exact=True) + B = [0.5*(n-d+1), 0.5*(n-d+2), n+1] + T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[n+1],B,[(n+1)*0.5,(n+2)*0.5],B,r)] + return T + v = ctx.hypercomb(h, [n,d], **kwargs) + else: + def h(n): + w = ctx.fmul(z, 0.5, exact=True) + r = ctx.fmul(w, w, prec=max(0,ctx.prec+M)) + return [([w], [n], [], [n+1], [], [n+1], r)] + v = ctx.hypercomb(h, [n], **kwargs) + return v + +@defun_wrapped +def bessely(ctx, n, z, derivative=0, **kwargs): + if not z: + if derivative: + # Not implemented + raise ValueError + if not n: + # ~ log(z/2) + return -ctx.inf + (n+z) + if ctx.im(n): + return ctx.nan * (n+z) + r = ctx.re(n) + q = n+0.5 + if ctx.isint(q): + if n > 0: + return -ctx.inf + (n+z) + else: + return 0 * (n+z) + if r < 0 and int(ctx.floor(q)) % 2: + return ctx.inf + (n+z) + else: + return ctx.ninf + (n+z) + # XXX: use hypercomb + ctx.prec += 10 + m, d = ctx.nint_distance(n) + if d < -ctx.prec: + h = +ctx.eps + ctx.prec *= 2 + n += h + elif d < 0: + ctx.prec -= d + # TODO: avoid cancellation for imaginary arguments + cos, sin = ctx.cospi_sinpi(n) + return (ctx.besselj(n,z,derivative,**kwargs)*cos - \ + ctx.besselj(-n,z,derivative,**kwargs))/sin + +@defun_wrapped +def besselk(ctx, n, z, **kwargs): + if not z: + return ctx.inf + M = ctx.mag(z) + if M < 1: + # Represent as limit definition + def h(n): + r = (z/2)**2 + T1 = [z, 2], [-n, n-1], [n], [], [], [1-n], r + T2 = [z, 2], [n, -n-1], [-n], [], [], [1+n], r + return T1, T2 + # We could use the limit definition always, but it leads + # to very bad cancellation (of exponentially large terms) + # for large real z + # Instead represent in terms of 2F0 + else: + ctx.prec += M + def h(n): + return [([ctx.pi/2, z, ctx.exp(-z)], [0.5,-0.5,1], [], [], \ + [n+0.5, 0.5-n], [], -1/(2*z))] + return ctx.hypercomb(h, [n], **kwargs) + +@defun_wrapped +def hankel1(ctx,n,x,**kwargs): + return ctx.besselj(n,x,**kwargs) + ctx.j*ctx.bessely(n,x,**kwargs) + +@defun_wrapped +def hankel2(ctx,n,x,**kwargs): + return ctx.besselj(n,x,**kwargs) - ctx.j*ctx.bessely(n,x,**kwargs) + +@defun_wrapped +def whitm(ctx,k,m,z,**kwargs): + if z == 0: + # M(k,m,z) = 0^(1/2+m) + if ctx.re(m) > -0.5: + return z + elif ctx.re(m) < -0.5: + return ctx.inf + z + else: + return ctx.nan * z + x = ctx.fmul(-0.5, z, exact=True) + y = 0.5+m + return ctx.exp(x) * z**y * ctx.hyp1f1(y-k, 1+2*m, z, **kwargs) + +@defun_wrapped +def whitw(ctx,k,m,z,**kwargs): + if z == 0: + g = abs(ctx.re(m)) + if g < 0.5: + return z + elif g > 0.5: + return ctx.inf + z + else: + return ctx.nan * z + x = ctx.fmul(-0.5, z, exact=True) + y = 0.5+m + return ctx.exp(x) * z**y * ctx.hyperu(y-k, 1+2*m, z, **kwargs) + +@defun +def hyperu(ctx, a, b, z, **kwargs): + a, atype = ctx._convert_param(a) + b, btype = ctx._convert_param(b) + z = ctx.convert(z) + if not z: + if ctx.re(b) <= 1: + return ctx.gammaprod([1-b],[a-b+1]) + else: + return ctx.inf + z + bb = 1+a-b + bb, bbtype = ctx._convert_param(bb) + try: + orig = ctx.prec + try: + ctx.prec += 10 + v = ctx.hypsum(2, 0, (atype, bbtype), [a, bb], -1/z, maxterms=ctx.prec) + return v / z**a + finally: + ctx.prec = orig + except ctx.NoConvergence: + pass + def h(a,b): + w = ctx.sinpi(b) + T1 = ([ctx.pi,w],[1,-1],[],[a-b+1,b],[a],[b],z) + T2 = ([-ctx.pi,w,z],[1,-1,1-b],[],[a,2-b],[a-b+1],[2-b],z) + return T1, T2 + return ctx.hypercomb(h, [a,b], **kwargs) + +@defun +def struveh(ctx,n,z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/StruveH/26/01/02/ + def h(n): + return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], -(z/2)**2)] + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def struvel(ctx,n,z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/StruveL/26/01/02/ + def h(n): + return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], (z/2)**2)] + return ctx.hypercomb(h, [n], **kwargs) + +def _anger(ctx,which,v,z,**kwargs): + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + def h(v): + b = ctx.mpq_1_2 + u = v*b + m = b*3 + a1,a2,b1,b2 = m-u, m+u, 1-u, 1+u + c, s = ctx.cospi_sinpi(u) + if which == 0: + A, B = [b*z, s], [c] + if which == 1: + A, B = [b*z, -c], [s] + w = ctx.square_exp_arg(z, mult=-0.25) + T1 = A, [1, 1], [], [a1,a2], [1], [a1,a2], w + T2 = B, [1], [], [b1,b2], [1], [b1,b2], w + return T1, T2 + return ctx.hypercomb(h, [v], **kwargs) + +@defun +def angerj(ctx, v, z, **kwargs): + return _anger(ctx, 0, v, z, **kwargs) + +@defun +def webere(ctx, v, z, **kwargs): + return _anger(ctx, 1, v, z, **kwargs) + +@defun +def lommels1(ctx, u, v, z, **kwargs): + u = ctx._convert_param(u)[0] + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + def h(u,v): + b = ctx.mpq_1_2 + w = ctx.square_exp_arg(z, mult=-0.25) + return ([u-v+1, u+v+1, z], [-1, -1, u+1], [], [], [1], \ + [b*(u-v+3),b*(u+v+3)], w), + return ctx.hypercomb(h, [u,v], **kwargs) + +@defun +def lommels2(ctx, u, v, z, **kwargs): + u = ctx._convert_param(u)[0] + v = ctx._convert_param(v)[0] + z = ctx.convert(z) + # Asymptotic expansion (GR p. 947) -- need to be careful + # not to use for small arguments + # def h(u,v): + # b = ctx.mpq_1_2 + # w = -(z/2)**(-2) + # return ([z], [u-1], [], [], [b*(1-u+v)], [b*(1-u-v)], w), + def h(u,v): + b = ctx.mpq_1_2 + w = ctx.square_exp_arg(z, mult=-0.25) + T1 = [u-v+1, u+v+1, z], [-1, -1, u+1], [], [], [1], [b*(u-v+3),b*(u+v+3)], w + T2 = [2, z], [u+v-1, -v], [v, b*(u+v+1)], [b*(v-u+1)], [], [1-v], w + T3 = [2, z], [u-v-1, v], [-v, b*(u-v+1)], [b*(1-u-v)], [], [1+v], w + #c1 = ctx.cospi((u-v)*b) + #c2 = ctx.cospi((u+v)*b) + #s = ctx.sinpi(v) + #r1 = (u-v+1)*b + #r2 = (u+v+1)*b + #T2 = [c1, s, z, 2], [1, -1, -v, v], [], [-v+1], [], [-v+1], w + #T3 = [-c2, s, z, 2], [1, -1, v, -v], [], [v+1], [], [v+1], w + #T2 = [c1, s, z, 2], [1, -1, -v, v+u-1], [r1, r2], [-v+1], [], [-v+1], w + #T3 = [-c2, s, z, 2], [1, -1, v, -v+u-1], [r1, r2], [v+1], [], [v+1], w + return T1, T2, T3 + return ctx.hypercomb(h, [u,v], **kwargs) + +@defun +def ber(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBer2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos, sin = ctx.cospi_sinpi(-0.75*n) + T1 = [cos, z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r + T2 = [sin, z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r + return T1, T2 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def bei(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBei2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos, sin = ctx.cospi_sinpi(0.75*n) + T1 = [cos, z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r + T2 = [sin, z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r + return T1, T2 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def ker(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKer2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos1, sin1 = ctx.cospi_sinpi(0.25*n) + cos2, sin2 = ctx.cospi_sinpi(0.75*n) + T1 = [2, z, 4*cos1], [-n-3, n, 1], [-n], [], [], [0.5, 0.5*(1+n), 0.5*(n+2)], r + T2 = [2, z, -sin1], [-n-3, 2+n, 1], [-n-1], [], [], [1.5, 0.5*(3+n), 0.5*(n+2)], r + T3 = [2, z, 4*cos2], [n-3, -n, 1], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r + T4 = [2, z, -sin2], [n-3, 2-n, 1], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r + return T1, T2, T3, T4 + return ctx.hypercomb(h, [n], **kwargs) + +@defun +def kei(ctx, n, z, **kwargs): + n = ctx.convert(n) + z = ctx.convert(z) + # http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKei2/26/01/02/0001/ + def h(n): + r = -(z/4)**4 + cos1, sin1 = ctx.cospi_sinpi(0.75*n) + cos2, sin2 = ctx.cospi_sinpi(0.25*n) + T1 = [-cos1, 2, z], [1, n-3, 2-n], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r + T2 = [-sin1, 2, z], [1, n-1, -n], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r + T3 = [-sin2, 2, z], [1, -n-1, n], [-n], [], [], [0.5, 0.5*(n+1), 0.5*(n+2)], r + T4 = [-cos2, 2, z], [1, -n-3, n+2], [-n-1], [], [], [1.5, 0.5*(n+3), 0.5*(n+2)], r + return T1, T2, T3, T4 + return ctx.hypercomb(h, [n], **kwargs) + +# TODO: do this more generically? +def c_memo(f): + name = f.__name__ + def f_wrapped(ctx): + cache = ctx._misc_const_cache + prec = ctx.prec + p,v = cache.get(name, (-1,0)) + if p >= prec: + return +v + else: + cache[name] = (prec, f(ctx)) + return cache[name][1] + return f_wrapped + +@c_memo +def _airyai_C1(ctx): + return 1 / (ctx.cbrt(9) * ctx.gamma(ctx.mpf(2)/3)) + +@c_memo +def _airyai_C2(ctx): + return -1 / (ctx.cbrt(3) * ctx.gamma(ctx.mpf(1)/3)) + +@c_memo +def _airybi_C1(ctx): + return 1 / (ctx.nthroot(3,6) * ctx.gamma(ctx.mpf(2)/3)) + +@c_memo +def _airybi_C2(ctx): + return ctx.nthroot(3,6) / ctx.gamma(ctx.mpf(1)/3) + +def _airybi_n2_inf(ctx): + prec = ctx.prec + try: + v = ctx.power(3,'2/3')*ctx.gamma('2/3')/(2*ctx.pi) + finally: + ctx.prec = prec + return +v + +# Derivatives at z = 0 +# TODO: could be expressed more elegantly using triple factorials +def _airyderiv_0(ctx, z, n, ntype, which): + if ntype == 'Z': + if n < 0: + return z + r = ctx.mpq_1_3 + prec = ctx.prec + try: + ctx.prec += 10 + v = ctx.gamma((n+1)*r) * ctx.power(3,n*r) / ctx.pi + if which == 0: + v *= ctx.sinpi(2*(n+1)*r) + v /= ctx.power(3,'2/3') + else: + v *= abs(ctx.sinpi(2*(n+1)*r)) + v /= ctx.power(3,'1/6') + finally: + ctx.prec = prec + return +v + z + else: + # singular (does the limit exist?) + raise NotImplementedError + +@defun +def airyai(ctx, z, derivative=0, **kwargs): + z = ctx.convert(z) + if derivative: + n, ntype = ctx._convert_param(derivative) + else: + n = 0 + # Values at infinities + if not ctx.isnormal(z) and z: + if n and ntype == 'Z': + if n == -1: + if z == ctx.inf: + return ctx.mpf(1)/3 + 1/z + if z == ctx.ninf: + return ctx.mpf(-2)/3 + 1/z + if n < -1: + if z == ctx.inf: + return z + if z == ctx.ninf: + return (-1)**n * (-z) + if (not n) and z == ctx.inf or z == ctx.ninf: + return 1/z + # TODO: limits + raise ValueError("essential singularity of Ai(z)") + # Account for exponential scaling + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if n: + if n == 1: + def h(): + # http://functions.wolfram.com/03.07.06.0005.01 + if ctx._re(z) > 4: + ctx.prec += extraprec + w = z**1.5; r = -0.75/w; u = -2*w/3 + ctx.prec -= extraprec + C = -ctx.exp(u)/(2*ctx.sqrt(ctx.pi))*ctx.nthroot(z,4) + return ([C],[1],[],[],[(-1,6),(7,6)],[],r), + # http://functions.wolfram.com/03.07.26.0001.01 + else: + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airyai_C1(ctx) * 0.5 + C2 = _airyai_C2(ctx) + T1 = [C1,z],[1,2],[],[],[],[ctx.mpq_5_3],w + T2 = [C2],[1],[],[],[],[ctx.mpq_1_3],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + else: + if z == 0: + return _airyderiv_0(ctx, z, n, ntype, 0) + # http://functions.wolfram.com/03.05.20.0004.01 + def h(n): + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + q13,q23,q43 = ctx.mpq_1_3, ctx.mpq_2_3, ctx.mpq_4_3 + a1=q13; a2=1; b1=(1-n)*q13; b2=(2-n)*q13; b3=1-n*q13 + T1 = [3, z], [n-q23, -n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + a1=q23; b1=(2-n)*q13; b2=1-n*q13; b3=(4-n)*q13 + T2 = [3, z, -z], [n-q43, -n, 1], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + return T1, T2 + v = ctx.hypercomb(h, [n], **kwargs) + if ctx._is_real_type(z) and ctx.isint(n): + v = ctx._re(v) + return v + else: + def h(): + if ctx._re(z) > 4: + # We could use 1F1, but it results in huge cancellation; + # the following expansion is better. + # TODO: asymptotic series for derivatives + ctx.prec += extraprec + w = z**1.5; r = -0.75/w; u = -2*w/3 + ctx.prec -= extraprec + C = ctx.exp(u)/(2*ctx.sqrt(ctx.pi)*ctx.nthroot(z,4)) + return ([C],[1],[],[],[(1,6),(5,6)],[],r), + else: + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airyai_C1(ctx) + C2 = _airyai_C2(ctx) + T1 = [C1],[1],[],[],[],[ctx.mpq_2_3],w + T2 = [z*C2],[1],[],[],[],[ctx.mpq_4_3],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def airybi(ctx, z, derivative=0, **kwargs): + z = ctx.convert(z) + if derivative: + n, ntype = ctx._convert_param(derivative) + else: + n = 0 + # Values at infinities + if not ctx.isnormal(z) and z: + if n and ntype == 'Z': + if z == ctx.inf: + return z + if z == ctx.ninf: + if n == -1: + return 1/z + if n == -2: + return _airybi_n2_inf(ctx) + if n < -2: + return (-1)**n * (-z) + if not n: + if z == ctx.inf: + return z + if z == ctx.ninf: + return 1/z + # TODO: limits + raise ValueError("essential singularity of Bi(z)") + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if n: + if n == 1: + # http://functions.wolfram.com/03.08.26.0001.01 + def h(): + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airybi_C1(ctx)*0.5 + C2 = _airybi_C2(ctx) + T1 = [C1,z],[1,2],[],[],[],[ctx.mpq_5_3],w + T2 = [C2],[1],[],[],[],[ctx.mpq_1_3],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + else: + if z == 0: + return _airyderiv_0(ctx, z, n, ntype, 1) + def h(n): + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + q13,q23,q43 = ctx.mpq_1_3, ctx.mpq_2_3, ctx.mpq_4_3 + q16 = ctx.mpq_1_6 + q56 = ctx.mpq_5_6 + a1=q13; a2=1; b1=(1-n)*q13; b2=(2-n)*q13; b3=1-n*q13 + T1 = [3, z], [n-q16, -n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + a1=q23; b1=(2-n)*q13; b2=1-n*q13; b3=(4-n)*q13 + T2 = [3, z], [n-q56, 1-n], [a1], [b1,b2,b3], \ + [a1,a2], [b1,b2,b3], w + return T1, T2 + v = ctx.hypercomb(h, [n], **kwargs) + if ctx._is_real_type(z) and ctx.isint(n): + v = ctx._re(v) + return v + else: + def h(): + ctx.prec += extraprec + w = z**3 / 9 + ctx.prec -= extraprec + C1 = _airybi_C1(ctx) + C2 = _airybi_C2(ctx) + T1 = [C1],[1],[],[],[],[ctx.mpq_2_3],w + T2 = [z*C2],[1],[],[],[],[ctx.mpq_4_3],w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +def _airy_zero(ctx, which, k, derivative, complex=False): + # Asymptotic formulas are given in DLMF section 9.9 + def U(t): return t**(2/3.)*(1-7/(t**2*48)) + def T(t): return t**(2/3.)*(1+5/(t**2*48)) + k = int(k) + if k < 1: + raise ValueError("k cannot be less than 1") + if not derivative in (0,1): + raise ValueError("Derivative should lie between 0 and 1") + if which == 0: + if derivative: + return ctx.findroot(lambda z: ctx.airyai(z,1), + -U(3*ctx.pi*(4*k-3)/8)) + return ctx.findroot(ctx.airyai, -T(3*ctx.pi*(4*k-1)/8)) + if which == 1 and complex == False: + if derivative: + return ctx.findroot(lambda z: ctx.airybi(z,1), + -U(3*ctx.pi*(4*k-1)/8)) + return ctx.findroot(ctx.airybi, -T(3*ctx.pi*(4*k-3)/8)) + if which == 1 and complex == True: + if derivative: + t = 3*ctx.pi*(4*k-3)/8 + 0.75j*ctx.ln2 + s = ctx.expjpi(ctx.mpf(1)/3) * T(t) + return ctx.findroot(lambda z: ctx.airybi(z,1), s) + t = 3*ctx.pi*(4*k-1)/8 + 0.75j*ctx.ln2 + s = ctx.expjpi(ctx.mpf(1)/3) * U(t) + return ctx.findroot(ctx.airybi, s) + +@defun +def airyaizero(ctx, k, derivative=0): + return _airy_zero(ctx, 0, k, derivative, False) + +@defun +def airybizero(ctx, k, derivative=0, complex=False): + return _airy_zero(ctx, 1, k, derivative, complex) + +def _scorer(ctx, z, which, kwargs): + z = ctx.convert(z) + if ctx.isinf(z): + if z == ctx.inf: + if which == 0: return 1/z + if which == 1: return z + if z == ctx.ninf: + return 1/z + raise ValueError("essential singularity") + if z: + extraprec = max(0, int(1.5*ctx.mag(z))) + else: + extraprec = 0 + if kwargs.get('derivative'): + raise NotImplementedError + # Direct asymptotic expansions, to avoid + # exponentially large cancellation + try: + if ctx.mag(z) > 3: + if which == 0 and abs(ctx.arg(z)) < ctx.pi/3 * 0.999: + def h(): + return (([ctx.pi,z],[-1,-1],[],[],[(1,3),(2,3),1],[],9/z**3),) + return ctx.hypercomb(h, [], maxterms=ctx.prec, force_series=True) + if which == 1 and abs(ctx.arg(-z)) < 2*ctx.pi/3 * 0.999: + def h(): + return (([-ctx.pi,z],[-1,-1],[],[],[(1,3),(2,3),1],[],9/z**3),) + return ctx.hypercomb(h, [], maxterms=ctx.prec, force_series=True) + except ctx.NoConvergence: + pass + def h(): + A = ctx.airybi(z, **kwargs)/3 + B = -2*ctx.pi + if which == 1: + A *= 2 + B *= -1 + ctx.prec += extraprec + w = z**3/9 + ctx.prec -= extraprec + T1 = [A], [1], [], [], [], [], 0 + T2 = [B,z], [-1,2], [], [], [1], [ctx.mpq_4_3,ctx.mpq_5_3], w + return T1, T2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def scorergi(ctx, z, **kwargs): + return _scorer(ctx, z, 0, kwargs) + +@defun +def scorerhi(ctx, z, **kwargs): + return _scorer(ctx, z, 1, kwargs) + +@defun_wrapped +def coulombc(ctx, l, eta, _cache={}): + if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec: + return +_cache[l,eta][1] + G3 = ctx.loggamma(2*l+2) + G1 = ctx.loggamma(1+l+ctx.j*eta) + G2 = ctx.loggamma(1+l-ctx.j*eta) + v = 2**l * ctx.exp((-ctx.pi*eta+G1+G2)/2 - G3) + if not (ctx.im(l) or ctx.im(eta)): + v = ctx.re(v) + _cache[l,eta] = (ctx.prec, v) + return v + +@defun_wrapped +def coulombf(ctx, l, eta, z, w=1, chop=True, **kwargs): + # Regular Coulomb wave function + # Note: w can be either 1 or -1; the other may be better in some cases + # TODO: check that chop=True chops when and only when it should + #ctx.prec += 10 + def h(l, eta): + try: + jw = ctx.j*w + jwz = ctx.fmul(jw, z, exact=True) + jwz2 = ctx.fmul(jwz, -2, exact=True) + C = ctx.coulombc(l, eta) + T1 = [C, z, ctx.exp(jwz)], [1, l+1, 1], [], [], [1+l+jw*eta], \ + [2*l+2], jwz2 + except ValueError: + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + v = ctx.hypercomb(h, [l,eta], **kwargs) + if chop and (not ctx.im(l)) and (not ctx.im(eta)) and (not ctx.im(z)) and \ + (ctx.re(z) >= 0): + v = ctx.re(v) + return v + +@defun_wrapped +def _coulomb_chi(ctx, l, eta, _cache={}): + if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec: + return _cache[l,eta][1] + def terms(): + l2 = -l-1 + jeta = ctx.j*eta + return [ctx.loggamma(1+l+jeta) * (-0.5j), + ctx.loggamma(1+l-jeta) * (0.5j), + ctx.loggamma(1+l2+jeta) * (0.5j), + ctx.loggamma(1+l2-jeta) * (-0.5j), + -(l+0.5)*ctx.pi] + v = ctx.sum_accurately(terms, 1) + _cache[l,eta] = (ctx.prec, v) + return v + +@defun_wrapped +def coulombg(ctx, l, eta, z, w=1, chop=True, **kwargs): + # Irregular Coulomb wave function + # Note: w can be either 1 or -1; the other may be better in some cases + # TODO: check that chop=True chops when and only when it should + if not ctx._im(l): + l = ctx._re(l) # XXX: for isint + def h(l, eta): + # Force perturbation for integers and half-integers + if ctx.isint(l*2): + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + l2 = -l-1 + try: + chi = ctx._coulomb_chi(l, eta) + jw = ctx.j*w + s = ctx.sin(chi); c = ctx.cos(chi) + C1 = ctx.coulombc(l,eta) + C2 = ctx.coulombc(l2,eta) + u = ctx.exp(jw*z) + x = -2*jw*z + T1 = [s, C1, z, u, c], [-1, 1, l+1, 1, 1], [], [], \ + [1+l+jw*eta], [2*l+2], x + T2 = [-s, C2, z, u], [-1, 1, l2+1, 1], [], [], \ + [1+l2+jw*eta], [2*l2+2], x + return T1, T2 + except ValueError: + T1 = [0], [-1], [], [], [], [], 0 + return (T1,) + v = ctx.hypercomb(h, [l,eta], **kwargs) + if chop and (not ctx._im(l)) and (not ctx._im(eta)) and (not ctx._im(z)) and \ + (ctx._re(z) >= 0): + v = ctx._re(v) + return v + +def mcmahon(ctx,kind,prime,v,m): + """ + Computes an estimate for the location of the Bessel function zero + j_{v,m}, y_{v,m}, j'_{v,m} or y'_{v,m} using McMahon's asymptotic + expansion (Abramowitz & Stegun 9.5.12-13, DLMF 20.21(vi)). + + Returns (r,err) where r is the estimated location of the root + and err is a positive number estimating the error of the + asymptotic expansion. + """ + u = 4*v**2 + if kind == 1 and not prime: b = (4*m+2*v-1)*ctx.pi/4 + if kind == 2 and not prime: b = (4*m+2*v-3)*ctx.pi/4 + if kind == 1 and prime: b = (4*m+2*v-3)*ctx.pi/4 + if kind == 2 and prime: b = (4*m+2*v-1)*ctx.pi/4 + if not prime: + s1 = b + s2 = -(u-1)/(8*b) + s3 = -4*(u-1)*(7*u-31)/(3*(8*b)**3) + s4 = -32*(u-1)*(83*u**2-982*u+3779)/(15*(8*b)**5) + s5 = -64*(u-1)*(6949*u**3-153855*u**2+1585743*u-6277237)/(105*(8*b)**7) + if prime: + s1 = b + s2 = -(u+3)/(8*b) + s3 = -4*(7*u**2+82*u-9)/(3*(8*b)**3) + s4 = -32*(83*u**3+2075*u**2-3039*u+3537)/(15*(8*b)**5) + s5 = -64*(6949*u**4+296492*u**3-1248002*u**2+7414380*u-5853627)/(105*(8*b)**7) + terms = [s1,s2,s3,s4,s5] + s = s1 + err = 0.0 + for i in range(1,len(terms)): + if abs(terms[i]) < abs(terms[i-1]): + s += terms[i] + else: + err = abs(terms[i]) + if i == len(terms)-1: + err = abs(terms[-1]) + return s, err + +def generalized_bisection(ctx,f,a,b,n): + """ + Given f known to have exactly n simple roots within [a,b], + return a list of n intervals isolating the roots + and having opposite signs at the endpoints. + + TODO: this can be optimized, e.g. by reusing evaluation points. + """ + if n < 1: + raise ValueError("n cannot be less than 1") + N = n+1 + points = [] + signs = [] + while 1: + points = ctx.linspace(a,b,N) + signs = [ctx.sign(f(x)) for x in points] + ok_intervals = [(points[i],points[i+1]) for i in range(N-1) \ + if signs[i]*signs[i+1] == -1] + if len(ok_intervals) == n: + return ok_intervals + N = N*2 + +def find_in_interval(ctx, f, ab): + return ctx.findroot(f, ab, solver='illinois', verify=False) + +def bessel_zero(ctx, kind, prime, v, m, isoltol=0.01, _interval_cache={}): + prec = ctx.prec + workprec = max(prec, ctx.mag(v), ctx.mag(m))+10 + try: + ctx.prec = workprec + v = ctx.mpf(v) + m = int(m) + prime = int(prime) + if v < 0: + raise ValueError("v cannot be negative") + if m < 1: + raise ValueError("m cannot be less than 1") + if not prime in (0,1): + raise ValueError("prime should lie between 0 and 1") + if kind == 1: + if prime: f = lambda x: ctx.besselj(v,x,derivative=1) + else: f = lambda x: ctx.besselj(v,x) + if kind == 2: + if prime: f = lambda x: ctx.bessely(v,x,derivative=1) + else: f = lambda x: ctx.bessely(v,x) + # The first root of J' is very close to 0 for small + # orders, and this needs to be special-cased + if kind == 1 and prime and m == 1: + if v == 0: + return ctx.zero + if v <= 1: + # TODO: use v <= j'_{v,1} < y_{v,1}? + r = 2*ctx.sqrt(v*(1+v)/(v+2)) + return find_in_interval(ctx, f, (r/10, 2*r)) + if (kind,prime,v,m) in _interval_cache: + return find_in_interval(ctx, f, _interval_cache[kind,prime,v,m]) + r, err = mcmahon(ctx, kind, prime, v, m) + if err < isoltol: + return find_in_interval(ctx, f, (r-isoltol, r+isoltol)) + # An x such that 0 < x < r_{v,1} + if kind == 1 and not prime: low = 2.4 + if kind == 1 and prime: low = 1.8 + if kind == 2 and not prime: low = 0.8 + if kind == 2 and prime: low = 2.0 + n = m+1 + while 1: + r1, err = mcmahon(ctx, kind, prime, v, n) + if err < isoltol: + r2, err2 = mcmahon(ctx, kind, prime, v, n+1) + intervals = generalized_bisection(ctx, f, low, 0.5*(r1+r2), n) + for k, ab in enumerate(intervals): + _interval_cache[kind,prime,v,k+1] = ab + return find_in_interval(ctx, f, intervals[m-1]) + else: + n = n*2 + finally: + ctx.prec = prec + +@defun +def besseljzero(ctx, v, m, derivative=0): + r""" + For a real order `\nu \ge 0` and a positive integer `m`, returns + `j_{\nu,m}`, the `m`-th positive zero of the Bessel function of the + first kind `J_{\nu}(z)` (see :func:`~mpmath.besselj`). Alternatively, + with *derivative=1*, gives the first nonnegative simple zero + `j'_{\nu,m}` of `J'_{\nu}(z)`. + + The indexing convention is that used by Abramowitz & Stegun + and the DLMF. Note the special case `j'_{0,1} = 0`, while all other + zeros are positive. In effect, only simple zeros are counted + (all zeros of Bessel functions are simple except possibly `z = 0`) + and `j_{\nu,m}` becomes a monotonic function of both `\nu` + and `m`. + + The zeros are interlaced according to the inequalities + + .. math :: + + j'_{\nu,k} < j_{\nu,k} < j'_{\nu,k+1} + + j_{\nu,1} < j_{\nu+1,2} < j_{\nu,2} < j_{\nu+1,2} < j_{\nu,3} < \cdots + + **Examples** + + Initial zeros of the Bessel functions `J_0(z), J_1(z), J_2(z)`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> besseljzero(0,1); besseljzero(0,2); besseljzero(0,3) + 2.404825557695772768621632 + 5.520078110286310649596604 + 8.653727912911012216954199 + >>> besseljzero(1,1); besseljzero(1,2); besseljzero(1,3) + 3.831705970207512315614436 + 7.01558666981561875353705 + 10.17346813506272207718571 + >>> besseljzero(2,1); besseljzero(2,2); besseljzero(2,3) + 5.135622301840682556301402 + 8.417244140399864857783614 + 11.61984117214905942709415 + + Initial zeros of `J'_0(z), J'_1(z), J'_2(z)`:: + + 0.0 + 3.831705970207512315614436 + 7.01558666981561875353705 + >>> besseljzero(1,1,1); besseljzero(1,2,1); besseljzero(1,3,1) + 1.84118378134065930264363 + 5.331442773525032636884016 + 8.536316366346285834358961 + >>> besseljzero(2,1,1); besseljzero(2,2,1); besseljzero(2,3,1) + 3.054236928227140322755932 + 6.706133194158459146634394 + 9.969467823087595793179143 + + Zeros with large index:: + + >>> besseljzero(0,100); besseljzero(0,1000); besseljzero(0,10000) + 313.3742660775278447196902 + 3140.807295225078628895545 + 31415.14114171350798533666 + >>> besseljzero(5,100); besseljzero(5,1000); besseljzero(5,10000) + 321.1893195676003157339222 + 3148.657306813047523500494 + 31422.9947255486291798943 + >>> besseljzero(0,100,1); besseljzero(0,1000,1); besseljzero(0,10000,1) + 311.8018681873704508125112 + 3139.236339643802482833973 + 31413.57032947022399485808 + + Zeros of functions with large order:: + + >>> besseljzero(50,1) + 57.11689916011917411936228 + >>> besseljzero(50,2) + 62.80769876483536093435393 + >>> besseljzero(50,100) + 388.6936600656058834640981 + >>> besseljzero(50,1,1) + 52.99764038731665010944037 + >>> besseljzero(50,2,1) + 60.02631933279942589882363 + >>> besseljzero(50,100,1) + 387.1083151608726181086283 + + Zeros of functions with fractional order:: + + >>> besseljzero(0.5,1); besseljzero(1.5,1); besseljzero(2.25,4) + 3.141592653589793238462643 + 4.493409457909064175307881 + 15.15657692957458622921634 + + Both `J_{\nu}(z)` and `J'_{\nu}(z)` can be expressed as infinite + products over their zeros:: + + >>> v,z = 2, mpf(1) + >>> (z/2)**v/gamma(v+1) * \ + ... nprod(lambda k: 1-(z/besseljzero(v,k))**2, [1,inf]) + ... + 0.1149034849319004804696469 + >>> besselj(v,z) + 0.1149034849319004804696469 + >>> (z/2)**(v-1)/2/gamma(v) * \ + ... nprod(lambda k: 1-(z/besseljzero(v,k,1))**2, [1,inf]) + ... + 0.2102436158811325550203884 + >>> besselj(v,z,1) + 0.2102436158811325550203884 + + """ + return +bessel_zero(ctx, 1, derivative, v, m) + +@defun +def besselyzero(ctx, v, m, derivative=0): + r""" + For a real order `\nu \ge 0` and a positive integer `m`, returns + `y_{\nu,m}`, the `m`-th positive zero of the Bessel function of the + second kind `Y_{\nu}(z)` (see :func:`~mpmath.bessely`). Alternatively, + with *derivative=1*, gives the first positive zero `y'_{\nu,m}` of + `Y'_{\nu}(z)`. + + The zeros are interlaced according to the inequalities + + .. math :: + + y_{\nu,k} < y'_{\nu,k} < y_{\nu,k+1} + + y_{\nu,1} < y_{\nu+1,2} < y_{\nu,2} < y_{\nu+1,2} < y_{\nu,3} < \cdots + + **Examples** + + Initial zeros of the Bessel functions `Y_0(z), Y_1(z), Y_2(z)`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> besselyzero(0,1); besselyzero(0,2); besselyzero(0,3) + 0.8935769662791675215848871 + 3.957678419314857868375677 + 7.086051060301772697623625 + >>> besselyzero(1,1); besselyzero(1,2); besselyzero(1,3) + 2.197141326031017035149034 + 5.429681040794135132772005 + 8.596005868331168926429606 + >>> besselyzero(2,1); besselyzero(2,2); besselyzero(2,3) + 3.384241767149593472701426 + 6.793807513268267538291167 + 10.02347797936003797850539 + + Initial zeros of `Y'_0(z), Y'_1(z), Y'_2(z)`:: + + >>> besselyzero(0,1,1); besselyzero(0,2,1); besselyzero(0,3,1) + 2.197141326031017035149034 + 5.429681040794135132772005 + 8.596005868331168926429606 + >>> besselyzero(1,1,1); besselyzero(1,2,1); besselyzero(1,3,1) + 3.683022856585177699898967 + 6.941499953654175655751944 + 10.12340465543661307978775 + >>> besselyzero(2,1,1); besselyzero(2,2,1); besselyzero(2,3,1) + 5.002582931446063945200176 + 8.350724701413079526349714 + 11.57419546521764654624265 + + Zeros with large index:: + + >>> besselyzero(0,100); besselyzero(0,1000); besselyzero(0,10000) + 311.8034717601871549333419 + 3139.236498918198006794026 + 31413.57034538691205229188 + >>> besselyzero(5,100); besselyzero(5,1000); besselyzero(5,10000) + 319.6183338562782156235062 + 3147.086508524556404473186 + 31421.42392920214673402828 + >>> besselyzero(0,100,1); besselyzero(0,1000,1); besselyzero(0,10000,1) + 313.3726705426359345050449 + 3140.807136030340213610065 + 31415.14112579761578220175 + + Zeros of functions with large order:: + + >>> besselyzero(50,1) + 53.50285882040036394680237 + >>> besselyzero(50,2) + 60.11244442774058114686022 + >>> besselyzero(50,100) + 387.1096509824943957706835 + >>> besselyzero(50,1,1) + 56.96290427516751320063605 + >>> besselyzero(50,2,1) + 62.74888166945933944036623 + >>> besselyzero(50,100,1) + 388.6923300548309258355475 + + Zeros of functions with fractional order:: + + >>> besselyzero(0.5,1); besselyzero(1.5,1); besselyzero(2.25,4) + 1.570796326794896619231322 + 2.798386045783887136720249 + 13.56721208770735123376018 + + """ + return +bessel_zero(ctx, 2, derivative, v, m) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/elliptic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/elliptic.py new file mode 100644 index 0000000000000000000000000000000000000000..1e198697fa042b7cc8bcba9e9e770f5c8106dad6 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/elliptic.py @@ -0,0 +1,1431 @@ +r""" +Elliptic functions historically comprise the elliptic integrals +and their inverses, and originate from the problem of computing the +arc length of an ellipse. From a more modern point of view, +an elliptic function is defined as a doubly periodic function, i.e. +a function which satisfies + +.. math :: + + f(z + 2 \omega_1) = f(z + 2 \omega_2) = f(z) + +for some half-periods `\omega_1, \omega_2` with +`\mathrm{Im}[\omega_1 / \omega_2] > 0`. The canonical elliptic +functions are the Jacobi elliptic functions. More broadly, this section +includes quasi-doubly periodic functions (such as the Jacobi theta +functions) and other functions useful in the study of elliptic functions. + +Many different conventions for the arguments of +elliptic functions are in use. It is even standard to use +different parameterizations for different functions in the same +text or software (and mpmath is no exception). +The usual parameters are the elliptic nome `q`, which usually +must satisfy `|q| < 1`; the elliptic parameter `m` (an arbitrary +complex number); the elliptic modulus `k` (an arbitrary complex +number); and the half-period ratio `\tau`, which usually must +satisfy `\mathrm{Im}[\tau] > 0`. +These quantities can be expressed in terms of each other +using the following relations: + +.. math :: + + m = k^2 + +.. math :: + + \tau = i \frac{K(1-m)}{K(m)} + +.. math :: + + q = e^{i \pi \tau} + +.. math :: + + k = \frac{\vartheta_2^2(q)}{\vartheta_3^2(q)} + +In addition, an alternative definition is used for the nome in +number theory, which we here denote by q-bar: + +.. math :: + + \bar{q} = q^2 = e^{2 i \pi \tau} + +For convenience, mpmath provides functions to convert +between the various parameters (:func:`~mpmath.qfrom`, :func:`~mpmath.mfrom`, +:func:`~mpmath.kfrom`, :func:`~mpmath.taufrom`, :func:`~mpmath.qbarfrom`). + +**References** + +1. [AbramowitzStegun]_ + +2. [WhittakerWatson]_ + +""" + +from .functions import defun, defun_wrapped + +@defun_wrapped +def eta(ctx, tau): + r""" + Returns the Dedekind eta function of tau in the upper half-plane. + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> eta(1j); gamma(0.25) / (2*pi**0.75) + (0.7682254223260566590025942 + 0.0j) + 0.7682254223260566590025942 + >>> tau = sqrt(2) + sqrt(5)*1j + >>> eta(-1/tau); sqrt(-1j*tau) * eta(tau) + (0.9022859908439376463573294 + 0.07985093673948098408048575j) + (0.9022859908439376463573295 + 0.07985093673948098408048575j) + >>> eta(tau+1); exp(pi*1j/12) * eta(tau) + (0.4493066139717553786223114 + 0.3290014793877986663915939j) + (0.4493066139717553786223114 + 0.3290014793877986663915939j) + >>> f = lambda z: diff(eta, z) / eta(z) + >>> chop(36*diff(f,tau)**2 - 24*diff(f,tau,2)*f(tau) + diff(f,tau,3)) + 0.0 + + """ + if ctx.im(tau) <= 0.0: + raise ValueError("eta is only defined in the upper half-plane") + q = ctx.expjpi(tau/12) + return q * ctx.qp(q**24) + +def nome(ctx, m): + m = ctx.convert(m) + if not m: + return m + if m == ctx.one: + return m + if ctx.isnan(m): + return m + if ctx.isinf(m): + if m == ctx.ninf: + return type(m)(-1) + else: + return ctx.mpc(-1) + a = ctx.ellipk(ctx.one-m) + b = ctx.ellipk(m) + v = ctx.exp(-ctx.pi*a/b) + if not ctx._im(m) and ctx._re(m) < 1: + if ctx._is_real_type(m): + return v.real + else: + return v.real + 0j + elif m == 2: + v = ctx.mpc(0, v.imag) + return v + +@defun_wrapped +def qfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic nome `q`, given any of `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qfrom(q=0.25) + 0.25 + >>> qfrom(m=mfrom(q=0.25)) + 0.25 + >>> qfrom(k=kfrom(q=0.25)) + 0.25 + >>> qfrom(tau=taufrom(q=0.25)) + (0.25 + 0.0j) + >>> qfrom(qbar=qbarfrom(q=0.25)) + 0.25 + + """ + if q is not None: + return ctx.convert(q) + if m is not None: + return nome(ctx, m) + if k is not None: + return nome(ctx, ctx.convert(k)**2) + if tau is not None: + return ctx.expjpi(tau) + if qbar is not None: + return ctx.sqrt(qbar) + +@defun_wrapped +def qbarfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the number-theoretic nome `\bar q`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qbarfrom(qbar=0.25) + 0.25 + >>> qbarfrom(q=qfrom(qbar=0.25)) + 0.25 + >>> qbarfrom(m=extraprec(20)(mfrom)(qbar=0.25)) # ill-conditioned + 0.25 + >>> qbarfrom(k=extraprec(20)(kfrom)(qbar=0.25)) # ill-conditioned + 0.25 + >>> qbarfrom(tau=taufrom(qbar=0.25)) + (0.25 + 0.0j) + + """ + if qbar is not None: + return ctx.convert(qbar) + if q is not None: + return ctx.convert(q) ** 2 + if m is not None: + return nome(ctx, m) ** 2 + if k is not None: + return nome(ctx, ctx.convert(k)**2) ** 2 + if tau is not None: + return ctx.expjpi(2*tau) + +@defun_wrapped +def taufrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic half-period ratio `\tau`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> taufrom(tau=0.5j) + (0.0 + 0.5j) + >>> taufrom(q=qfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(m=mfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(k=kfrom(tau=0.5j)) + (0.0 + 0.5j) + >>> taufrom(qbar=qbarfrom(tau=0.5j)) + (0.0 + 0.5j) + + """ + if tau is not None: + return ctx.convert(tau) + if m is not None: + m = ctx.convert(m) + return ctx.j*ctx.ellipk(1-m)/ctx.ellipk(m) + if k is not None: + k = ctx.convert(k) + return ctx.j*ctx.ellipk(1-k**2)/ctx.ellipk(k**2) + if q is not None: + return ctx.log(q) / (ctx.pi*ctx.j) + if qbar is not None: + qbar = ctx.convert(qbar) + return ctx.log(qbar) / (2*ctx.pi*ctx.j) + +@defun_wrapped +def kfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic modulus `k`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> kfrom(k=0.25) + 0.25 + >>> kfrom(m=mfrom(k=0.25)) + 0.25 + >>> kfrom(q=qfrom(k=0.25)) + 0.25 + >>> kfrom(tau=taufrom(k=0.25)) + (0.25 + 0.0j) + >>> kfrom(qbar=qbarfrom(k=0.25)) + 0.25 + + As `q \to 1` and `q \to -1`, `k` rapidly approaches + `1` and `i \infty` respectively:: + + >>> kfrom(q=0.75) + 0.9999999999999899166471767 + >>> kfrom(q=-0.75) + (0.0 + 7041781.096692038332790615j) + >>> kfrom(q=1) + 1 + >>> kfrom(q=-1) + (0.0 + +infj) + """ + if k is not None: + return ctx.convert(k) + if m is not None: + return ctx.sqrt(m) + if tau is not None: + q = ctx.expjpi(tau) + if qbar is not None: + q = ctx.sqrt(qbar) + if q == 1: + return q + if q == -1: + return ctx.mpc(0,'inf') + return (ctx.jtheta(2,0,q)/ctx.jtheta(3,0,q))**2 + +@defun_wrapped +def mfrom(ctx, q=None, m=None, k=None, tau=None, qbar=None): + r""" + Returns the elliptic parameter `m`, given any of + `q, m, k, \tau, \bar{q}`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> mfrom(m=0.25) + 0.25 + >>> mfrom(q=qfrom(m=0.25)) + 0.25 + >>> mfrom(k=kfrom(m=0.25)) + 0.25 + >>> mfrom(tau=taufrom(m=0.25)) + (0.25 + 0.0j) + >>> mfrom(qbar=qbarfrom(m=0.25)) + 0.25 + + As `q \to 1` and `q \to -1`, `m` rapidly approaches + `1` and `-\infty` respectively:: + + >>> mfrom(q=0.75) + 0.9999999999999798332943533 + >>> mfrom(q=-0.75) + -49586681013729.32611558353 + >>> mfrom(q=1) + 1.0 + >>> mfrom(q=-1) + -inf + + The inverse nome as a function of `q` has an integer + Taylor series expansion:: + + >>> taylor(lambda q: mfrom(q), 0, 7) + [0.0, 16.0, -128.0, 704.0, -3072.0, 11488.0, -38400.0, 117632.0] + + """ + if m is not None: + return m + if k is not None: + return k**2 + if tau is not None: + q = ctx.expjpi(tau) + if qbar is not None: + q = ctx.sqrt(qbar) + if q == 1: + return ctx.convert(q) + if q == -1: + return q*ctx.inf + v = (ctx.jtheta(2,0,q)/ctx.jtheta(3,0,q))**4 + if ctx._is_real_type(q) and q < 0: + v = v.real + return v + +jacobi_spec = { + 'sn' : ([3],[2],[1],[4], 'sin', 'tanh'), + 'cn' : ([4],[2],[2],[4], 'cos', 'sech'), + 'dn' : ([4],[3],[3],[4], '1', 'sech'), + 'ns' : ([2],[3],[4],[1], 'csc', 'coth'), + 'nc' : ([2],[4],[4],[2], 'sec', 'cosh'), + 'nd' : ([3],[4],[4],[3], '1', 'cosh'), + 'sc' : ([3],[4],[1],[2], 'tan', 'sinh'), + 'sd' : ([3,3],[2,4],[1],[3], 'sin', 'sinh'), + 'cd' : ([3],[2],[2],[3], 'cos', '1'), + 'cs' : ([4],[3],[2],[1], 'cot', 'csch'), + 'dc' : ([2],[3],[3],[2], 'sec', '1'), + 'ds' : ([2,4],[3,3],[3],[1], 'csc', 'csch'), + 'cc' : None, + 'ss' : None, + 'nn' : None, + 'dd' : None +} + +@defun +def ellipfun(ctx, kind, u=None, m=None, q=None, k=None, tau=None): + try: + S = jacobi_spec[kind] + except KeyError: + raise ValueError("First argument must be a two-character string " + "containing 's', 'c', 'd' or 'n', e.g.: 'sn'") + if u is None: + def f(*args, **kwargs): + return ctx.ellipfun(kind, *args, **kwargs) + f.__name__ = kind + return f + prec = ctx.prec + try: + ctx.prec += 10 + u = ctx.convert(u) + q = ctx.qfrom(m=m, q=q, k=k, tau=tau) + if S is None: + v = ctx.one + 0*q*u + elif q == ctx.zero: + if S[4] == '1': v = ctx.one + else: v = getattr(ctx, S[4])(u) + v += 0*q*u + elif q == ctx.one: + if S[5] == '1': v = ctx.one + else: v = getattr(ctx, S[5])(u) + v += 0*q*u + else: + t = u / ctx.jtheta(3, 0, q)**2 + v = ctx.one + for a in S[0]: v *= ctx.jtheta(a, 0, q) + for b in S[1]: v /= ctx.jtheta(b, 0, q) + for c in S[2]: v *= ctx.jtheta(c, t, q) + for d in S[3]: v /= ctx.jtheta(d, t, q) + finally: + ctx.prec = prec + return +v + +@defun_wrapped +def kleinj(ctx, tau=None, **kwargs): + r""" + Evaluates the Klein j-invariant, which is a modular function defined for + `\tau` in the upper half-plane as + + .. math :: + + J(\tau) = \frac{g_2^3(\tau)}{g_2^3(\tau) - 27 g_3^2(\tau)} + + where `g_2` and `g_3` are the modular invariants of the Weierstrass + elliptic function, + + .. math :: + + g_2(\tau) = 60 \sum_{(m,n) \in \mathbb{Z}^2 \setminus (0,0)} (m \tau+n)^{-4} + + g_3(\tau) = 140 \sum_{(m,n) \in \mathbb{Z}^2 \setminus (0,0)} (m \tau+n)^{-6}. + + An alternative, common notation is that of the j-function + `j(\tau) = 1728 J(\tau)`. + + **Plots** + + .. literalinclude :: /plots/kleinj.py + .. image :: /plots/kleinj.png + .. literalinclude :: /plots/kleinj2.py + .. image :: /plots/kleinj2.png + + **Examples** + + Verifying the functional equation `J(\tau) = J(\tau+1) = J(-\tau^{-1})`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> tau = 0.625+0.75*j + >>> tau = 0.625+0.75*j + >>> kleinj(tau) + (-0.1507492166511182267125242 + 0.07595948379084571927228948j) + >>> kleinj(tau+1) + (-0.1507492166511182267125242 + 0.07595948379084571927228948j) + >>> kleinj(-1/tau) + (-0.1507492166511182267125242 + 0.07595948379084571927228946j) + + The j-function has a famous Laurent series expansion in terms of the nome + `\bar{q}`, `j(\tau) = \bar{q}^{-1} + 744 + 196884\bar{q} + \ldots`:: + + >>> mp.dps = 15 + >>> taylor(lambda q: 1728*q*kleinj(qbar=q), 0, 5, singular=True) + [1.0, 744.0, 196884.0, 21493760.0, 864299970.0, 20245856256.0] + + The j-function admits exact evaluation at special algebraic points + related to the Heegner numbers 1, 2, 3, 7, 11, 19, 43, 67, 163:: + + >>> @extraprec(10) + ... def h(n): + ... v = (1+sqrt(n)*j) + ... if n > 2: + ... v *= 0.5 + ... return v + ... + >>> mp.dps = 25 + >>> for n in [1,2,3,7,11,19,43,67,163]: + ... n, chop(1728*kleinj(h(n))) + ... + (1, 1728.0) + (2, 8000.0) + (3, 0.0) + (7, -3375.0) + (11, -32768.0) + (19, -884736.0) + (43, -884736000.0) + (67, -147197952000.0) + (163, -262537412640768000.0) + + Also at other special points, the j-function assumes explicit + algebraic values, e.g.:: + + >>> chop(1728*kleinj(j*sqrt(5))) + 1264538.909475140509320227 + >>> identify(cbrt(_)) # note: not simplified + '((100+sqrt(13520))/2)' + >>> (50+26*sqrt(5))**3 + 1264538.909475140509320227 + + """ + q = ctx.qfrom(tau=tau, **kwargs) + t2 = ctx.jtheta(2,0,q) + t3 = ctx.jtheta(3,0,q) + t4 = ctx.jtheta(4,0,q) + P = (t2**8 + t3**8 + t4**8)**3 + Q = 54*(t2*t3*t4)**8 + return P/Q + + +def RF_calc(ctx, x, y, z, r): + if y == z: return RC_calc(ctx, x, y, r) + if x == z: return RC_calc(ctx, y, x, r) + if x == y: return RC_calc(ctx, z, x, r) + if not (ctx.isnormal(x) and ctx.isnormal(y) and ctx.isnormal(z)): + if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z): + return x*y*z + if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z): + return ctx.zero + xm,ym,zm = x,y,z + A0 = Am = (x+y+z)/3 + Q = ctx.root(3*r, -6) * max(abs(A0-x),abs(A0-y),abs(A0-z)) + g = ctx.mpf(0.25) + pow4 = ctx.one + while 1: + xs = ctx.sqrt(xm) + ys = ctx.sqrt(ym) + zs = ctx.sqrt(zm) + lm = xs*ys + xs*zs + ys*zs + Am1 = (Am+lm)*g + xm, ym, zm = (xm+lm)*g, (ym+lm)*g, (zm+lm)*g + if pow4 * Q < abs(Am): + break + Am = Am1 + pow4 *= g + t = pow4/Am + X = (A0-x)*t + Y = (A0-y)*t + Z = -X-Y + E2 = X*Y-Z**2 + E3 = X*Y*Z + return ctx.power(Am,-0.5) * (9240-924*E2+385*E2**2+660*E3-630*E2*E3)/9240 + +def RC_calc(ctx, x, y, r, pv=True): + if not (ctx.isnormal(x) and ctx.isnormal(y)): + if ctx.isinf(x) or ctx.isinf(y): + return 1/(x*y) + if y == 0: + return ctx.inf + if x == 0: + return ctx.pi / ctx.sqrt(y) / 2 + raise ValueError + # Cauchy principal value + if pv and ctx._im(y) == 0 and ctx._re(y) < 0: + return ctx.sqrt(x/(x-y)) * RC_calc(ctx, x-y, -y, r) + if x == y: + return 1/ctx.sqrt(x) + extraprec = 2*max(0,-ctx.mag(x-y)+ctx.mag(x)) + ctx.prec += extraprec + if ctx._is_real_type(x) and ctx._is_real_type(y): + x = ctx._re(x) + y = ctx._re(y) + a = ctx.sqrt(x/y) + if x < y: + b = ctx.sqrt(y-x) + v = ctx.acos(a)/b + else: + b = ctx.sqrt(x-y) + v = ctx.acosh(a)/b + else: + sx = ctx.sqrt(x) + sy = ctx.sqrt(y) + v = ctx.acos(sx/sy)/(ctx.sqrt((1-x/y))*sy) + ctx.prec -= extraprec + return v + +def RJ_calc(ctx, x, y, z, p, r, integration): + """ + With integration == 0, computes RJ only using Carlson's algorithm + (may be wrong for some values). + With integration == 1, uses an initial integration to make sure + Carlson's algorithm is correct. + With integration == 2, uses only integration. + """ + if not (ctx.isnormal(x) and ctx.isnormal(y) and \ + ctx.isnormal(z) and ctx.isnormal(p)): + if ctx.isnan(x) or ctx.isnan(y) or ctx.isnan(z) or ctx.isnan(p): + return x*y*z + if ctx.isinf(x) or ctx.isinf(y) or ctx.isinf(z) or ctx.isinf(p): + return ctx.zero + if not p: + return ctx.inf + if (not x) + (not y) + (not z) > 1: + return ctx.inf + # Check conditions and fall back on integration for argument + # reduction if needed. The following conditions might be needlessly + # restrictive. + initial_integral = ctx.zero + if integration >= 1: + ok = (x.real >= 0 and y.real >= 0 and z.real >= 0 and p.real > 0) + if not ok: + if x == p or y == p or z == p: + ok = True + if not ok: + if p.imag != 0 or p.real >= 0: + if (x.imag == 0 and x.real >= 0 and ctx.conj(y) == z): + ok = True + if (y.imag == 0 and y.real >= 0 and ctx.conj(x) == z): + ok = True + if (z.imag == 0 and z.real >= 0 and ctx.conj(x) == y): + ok = True + if not ok or (integration == 2): + N = ctx.ceil(-min(x.real, y.real, z.real, p.real)) + 1 + # Integrate around any singularities + if all((t.imag >= 0 or t.real > 0) for t in [x, y, z, p]): + margin = ctx.j + elif all((t.imag < 0 or t.real > 0) for t in [x, y, z, p]): + margin = -ctx.j + else: + margin = 1 + # Go through the upper half-plane, but low enough that any + # parameter starting in the lower plane doesn't cross the + # branch cut + for t in [x, y, z, p]: + if t.imag >= 0 or t.real > 0: + continue + margin = min(margin, abs(t.imag) * 0.5) + margin *= ctx.j + N += margin + F = lambda t: 1/(ctx.sqrt(t+x)*ctx.sqrt(t+y)*ctx.sqrt(t+z)*(t+p)) + if integration == 2: + return 1.5 * ctx.quadsubdiv(F, [0, N, ctx.inf]) + initial_integral = 1.5 * ctx.quadsubdiv(F, [0, N]) + x += N; y += N; z += N; p += N + xm,ym,zm,pm = x,y,z,p + A0 = Am = (x + y + z + 2*p)/5 + delta = (p-x)*(p-y)*(p-z) + Q = ctx.root(0.25*r, -6) * max(abs(A0-x),abs(A0-y),abs(A0-z),abs(A0-p)) + g = ctx.mpf(0.25) + pow4 = ctx.one + S = 0 + while 1: + sx = ctx.sqrt(xm) + sy = ctx.sqrt(ym) + sz = ctx.sqrt(zm) + sp = ctx.sqrt(pm) + lm = sx*sy + sx*sz + sy*sz + Am1 = (Am+lm)*g + xm = (xm+lm)*g; ym = (ym+lm)*g; zm = (zm+lm)*g; pm = (pm+lm)*g + dm = (sp+sx) * (sp+sy) * (sp+sz) + em = delta * pow4**3 / dm**2 + if pow4 * Q < abs(Am): + break + T = RC_calc(ctx, ctx.one, ctx.one+em, r) * pow4 / dm + S += T + pow4 *= g + Am = Am1 + t = pow4 / Am + X = (A0-x)*t + Y = (A0-y)*t + Z = (A0-z)*t + P = (-X-Y-Z)/2 + E2 = X*Y + X*Z + Y*Z - 3*P**2 + E3 = X*Y*Z + 2*E2*P + 4*P**3 + E4 = (2*X*Y*Z + E2*P + 3*P**3)*P + E5 = X*Y*Z*P**2 + P = 24024 - 5148*E2 + 2457*E2**2 + 4004*E3 - 4158*E2*E3 - 3276*E4 + 2772*E5 + Q = 24024 + v1 = pow4 * ctx.power(Am, -1.5) * P/Q + v2 = 6*S + return initial_integral + v1 + v2 + +@defun +def elliprf(ctx, x, y, z): + r""" + Evaluates the Carlson symmetric elliptic integral of the first kind + + .. math :: + + R_F(x,y,z) = \frac{1}{2} + \int_0^{\infty} \frac{dt}{\sqrt{(t+x)(t+y)(t+z)}} + + which is defined for `x,y,z \notin (-\infty,0)`, and with + at most one of `x,y,z` being zero. + + For real `x,y,z \ge 0`, the principal square root is taken in the integrand. + For complex `x,y,z`, the principal square root is taken as `t \to \infty` + and as `t \to 0` non-principal branches are chosen as necessary so as to + make the integrand continuous. + + **Examples** + + Some basic values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> elliprf(0,1,1); pi/2 + 1.570796326794896619231322 + 1.570796326794896619231322 + >>> elliprf(0,1,inf) + 0.0 + >>> elliprf(1,1,1) + 1.0 + >>> elliprf(2,2,2)**2 + 0.5 + >>> elliprf(1,0,0); elliprf(0,0,1); elliprf(0,1,0); elliprf(0,0,0) + +inf + +inf + +inf + +inf + + Representing complete elliptic integrals in terms of `R_F`:: + + >>> m = mpf(0.75) + >>> ellipk(m); elliprf(0,1-m,1) + 2.156515647499643235438675 + 2.156515647499643235438675 + >>> ellipe(m); elliprf(0,1-m,1)-m*elliprd(0,1-m,1)/3 + 1.211056027568459524803563 + 1.211056027568459524803563 + + Some symmetries and argument transformations:: + + >>> x,y,z = 2,3,4 + >>> elliprf(x,y,z); elliprf(y,x,z); elliprf(z,y,x) + 0.5840828416771517066928492 + 0.5840828416771517066928492 + 0.5840828416771517066928492 + >>> k = mpf(100000) + >>> elliprf(k*x,k*y,k*z); k**(-0.5) * elliprf(x,y,z) + 0.001847032121923321253219284 + 0.001847032121923321253219284 + >>> l = sqrt(x*y) + sqrt(y*z) + sqrt(z*x) + >>> elliprf(x,y,z); 2*elliprf(x+l,y+l,z+l) + 0.5840828416771517066928492 + 0.5840828416771517066928492 + >>> elliprf((x+l)/4,(y+l)/4,(z+l)/4) + 0.5840828416771517066928492 + + Comparing with numerical integration:: + + >>> x,y,z = 2,3,4 + >>> elliprf(x,y,z) + 0.5840828416771517066928492 + >>> f = lambda t: 0.5*((t+x)*(t+y)*(t+z))**(-0.5) + >>> q = extradps(25)(quad) + >>> q(f, [0,inf]) + 0.5840828416771517066928492 + + With the following arguments, the square root in the integrand becomes + discontinuous at `t = 1/2` if the principal branch is used. To obtain + the right value, `-\sqrt{r}` must be taken instead of `\sqrt{r}` + on `t \in (0, 1/2)`:: + + >>> x,y,z = j-1,j,0 + >>> elliprf(x,y,z) + (0.7961258658423391329305694 - 1.213856669836495986430094j) + >>> -q(f, [0,0.5]) + q(f, [0.5,inf]) + (0.7961258658423391329305694 - 1.213856669836495986430094j) + + The so-called *first lemniscate constant*, a transcendental number:: + + >>> elliprf(0,1,2) + 1.31102877714605990523242 + >>> extradps(25)(quad)(lambda t: 1/sqrt(1-t**4), [0,1]) + 1.31102877714605990523242 + >>> gamma('1/4')**2/(4*sqrt(2*pi)) + 1.31102877714605990523242 + + **References** + + 1. [Carlson]_ + 2. [DLMF]_ Chapter 19. Elliptic Integrals + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RF_calc(ctx, x, y, z, tol) + finally: + ctx.prec = prec + return +v + +@defun +def elliprc(ctx, x, y, pv=True): + r""" + Evaluates the degenerate Carlson symmetric elliptic integral + of the first kind + + .. math :: + + R_C(x,y) = R_F(x,y,y) = + \frac{1}{2} \int_0^{\infty} \frac{dt}{(t+y) \sqrt{(t+x)}}. + + If `y \in (-\infty,0)`, either a value defined by continuity, + or with *pv=True* the Cauchy principal value, can be computed. + + If `x \ge 0, y > 0`, the value can be expressed in terms of + elementary functions as + + .. math :: + + R_C(x,y) = + \begin{cases} + \dfrac{1}{\sqrt{y-x}} + \cos^{-1}\left(\sqrt{\dfrac{x}{y}}\right), & x < y \\ + \dfrac{1}{\sqrt{y}}, & x = y \\ + \dfrac{1}{\sqrt{x-y}} + \cosh^{-1}\left(\sqrt{\dfrac{x}{y}}\right), & x > y \\ + \end{cases}. + + **Examples** + + Some special values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> elliprc(1,2)*4; elliprc(0,1)*2; +pi + 3.141592653589793238462643 + 3.141592653589793238462643 + 3.141592653589793238462643 + >>> elliprc(1,0) + +inf + >>> elliprc(5,5)**2 + 0.2 + >>> elliprc(1,inf); elliprc(inf,1); elliprc(inf,inf) + 0.0 + 0.0 + 0.0 + + Comparing with the elementary closed-form solution:: + + >>> elliprc('1/3', '1/5'); sqrt(7.5)*acosh(sqrt('5/3')) + 2.041630778983498390751238 + 2.041630778983498390751238 + >>> elliprc('1/5', '1/3'); sqrt(7.5)*acos(sqrt('3/5')) + 1.875180765206547065111085 + 1.875180765206547065111085 + + Comparing with numerical integration:: + + >>> q = extradps(25)(quad) + >>> elliprc(2, -3, pv=True) + 0.3333969101113672670749334 + >>> elliprc(2, -3, pv=False) + (0.3333969101113672670749334 + 0.7024814731040726393156375j) + >>> 0.5*q(lambda t: 1/(sqrt(t+2)*(t-3)), [0,3-j,6,inf]) + (0.3333969101113672670749334 + 0.7024814731040726393156375j) + + """ + x = ctx.convert(x) + y = ctx.convert(y) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RC_calc(ctx, x, y, tol, pv) + finally: + ctx.prec = prec + return +v + +@defun +def elliprj(ctx, x, y, z, p, integration=1): + r""" + Evaluates the Carlson symmetric elliptic integral of the third kind + + .. math :: + + R_J(x,y,z,p) = \frac{3}{2} + \int_0^{\infty} \frac{dt}{(t+p)\sqrt{(t+x)(t+y)(t+z)}}. + + Like :func:`~mpmath.elliprf`, the branch of the square root in the integrand + is defined so as to be continuous along the path of integration for + complex values of the arguments. + + **Examples** + + Some values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> elliprj(1,1,1,1) + 1.0 + >>> elliprj(2,2,2,2); 1/(2*sqrt(2)) + 0.3535533905932737622004222 + 0.3535533905932737622004222 + >>> elliprj(0,1,2,2) + 1.067937989667395702268688 + >>> 3*(2*gamma('5/4')**2-pi**2/gamma('1/4')**2)/(sqrt(2*pi)) + 1.067937989667395702268688 + >>> elliprj(0,1,1,2); 3*pi*(2-sqrt(2))/4 + 1.380226776765915172432054 + 1.380226776765915172432054 + >>> elliprj(1,3,2,0); elliprj(0,1,1,0); elliprj(0,0,0,0) + +inf + +inf + +inf + >>> elliprj(1,inf,1,0); elliprj(1,1,1,inf) + 0.0 + 0.0 + >>> chop(elliprj(1+j, 1-j, 1, 1)) + 0.8505007163686739432927844 + + Scale transformation:: + + >>> x,y,z,p = 2,3,4,5 + >>> k = mpf(100000) + >>> elliprj(k*x,k*y,k*z,k*p); k**(-1.5)*elliprj(x,y,z,p) + 4.521291677592745527851168e-9 + 4.521291677592745527851168e-9 + + Comparing with numerical integration:: + + >>> elliprj(1,2,3,4) + 0.2398480997495677621758617 + >>> f = lambda t: 1/((t+4)*sqrt((t+1)*(t+2)*(t+3))) + >>> 1.5*quad(f, [0,inf]) + 0.2398480997495677621758617 + >>> elliprj(1,2+1j,3,4-2j) + (0.216888906014633498739952 + 0.04081912627366673332369512j) + >>> f = lambda t: 1/((t+4-2j)*sqrt((t+1)*(t+2+1j)*(t+3))) + >>> 1.5*quad(f, [0,inf]) + (0.216888906014633498739952 + 0.04081912627366673332369511j) + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + p = ctx.convert(p) + prec = ctx.prec + try: + ctx.prec += 20 + tol = ctx.eps * 2**10 + v = RJ_calc(ctx, x, y, z, p, tol, integration) + finally: + ctx.prec = prec + return +v + +@defun +def elliprd(ctx, x, y, z): + r""" + Evaluates the degenerate Carlson symmetric elliptic integral + of the third kind or Carlson elliptic integral of the + second kind `R_D(x,y,z) = R_J(x,y,z,z)`. + + See :func:`~mpmath.elliprj` for additional information. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> elliprd(1,2,3) + 0.2904602810289906442326534 + >>> elliprj(1,2,3,3) + 0.2904602810289906442326534 + + The so-called *second lemniscate constant*, a transcendental number:: + + >>> elliprd(0,2,1)/3 + 0.5990701173677961037199612 + >>> extradps(25)(quad)(lambda t: t**2/sqrt(1-t**4), [0,1]) + 0.5990701173677961037199612 + >>> gamma('3/4')**2/sqrt(2*pi) + 0.5990701173677961037199612 + + """ + return ctx.elliprj(x,y,z,z) + +@defun +def elliprg(ctx, x, y, z): + r""" + Evaluates the Carlson completely symmetric elliptic integral + of the second kind + + .. math :: + + R_G(x,y,z) = \frac{1}{4} \int_0^{\infty} + \frac{t}{\sqrt{(t+x)(t+y)(t+z)}} + \left( \frac{x}{t+x} + \frac{y}{t+y} + \frac{z}{t+z}\right) dt. + + **Examples** + + Evaluation for real and complex arguments:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> elliprg(0,1,1)*4; +pi + 3.141592653589793238462643 + 3.141592653589793238462643 + >>> elliprg(0,0.5,1) + 0.6753219405238377512600874 + >>> chop(elliprg(1+j, 1-j, 2)) + 1.172431327676416604532822 + + A double integral that can be evaluated in terms of `R_G`:: + + >>> x,y,z = 2,3,4 + >>> def f(t,u): + ... st = fp.sin(t); ct = fp.cos(t) + ... su = fp.sin(u); cu = fp.cos(u) + ... return (x*(st*cu)**2 + y*(st*su)**2 + z*ct**2)**0.5 * st + ... + >>> nprint(mpf(fp.quad(f, [0,fp.pi], [0,2*fp.pi])/(4*fp.pi)), 13) + 1.725503028069 + >>> nprint(elliprg(x,y,z), 13) + 1.725503028069 + + """ + x = ctx.convert(x) + y = ctx.convert(y) + z = ctx.convert(z) + zeros = (not x) + (not y) + (not z) + if zeros == 3: + return (x+y+z)*0 + if zeros == 2: + if x: return 0.5*ctx.sqrt(x) + if y: return 0.5*ctx.sqrt(y) + return 0.5*ctx.sqrt(z) + if zeros == 1: + if not z: + x, z = z, x + def terms(): + T1 = 0.5*z*ctx.elliprf(x,y,z) + T2 = -0.5*(x-z)*(y-z)*ctx.elliprd(x,y,z)/3 + T3 = 0.5*ctx.sqrt(x)*ctx.sqrt(y)/ctx.sqrt(z) + return T1,T2,T3 + return ctx.sum_accurately(terms) + + +@defun_wrapped +def ellipf(ctx, phi, m): + r""" + Evaluates the Legendre incomplete elliptic integral of the first kind + + .. math :: + + F(\phi,m) = \int_0^{\phi} \frac{dt}{\sqrt{1-m \sin^2 t}} + + or equivalently + + .. math :: + + F(\phi,m) = \int_0^{\sin \phi} + \frac{dt}{\left(\sqrt{1-t^2}\right)\left(\sqrt{1-mt^2}\right)}. + + The function reduces to a complete elliptic integral of the first kind + (see :func:`~mpmath.ellipk`) when `\phi = \frac{\pi}{2}`; that is, + + .. math :: + + F\left(\frac{\pi}{2}, m\right) = K(m). + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(\phi) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + F(\phi + n \pi, m) = 2 n K(m) + F(\phi,m), n \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellipf.py + .. image :: /plots/ellipf.png + + **Examples** + + Basic values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> ellipf(0,1) + 0.0 + >>> ellipf(0,0) + 0.0 + >>> ellipf(1,0); ellipf(2+3j,0) + 1.0 + (2.0 + 3.0j) + >>> ellipf(1,1); log(sec(1)+tan(1)) + 1.226191170883517070813061 + 1.226191170883517070813061 + >>> ellipf(pi/2, -0.5); ellipk(-0.5) + 1.415737208425956198892166 + 1.415737208425956198892166 + >>> ellipf(pi/2+eps, 1); ellipf(-pi/2-eps, 1) + +inf + +inf + >>> ellipf(1.5, 1) + 3.340677542798311003320813 + + Comparing with numerical integration:: + + >>> z,m = 0.5, 1.25 + >>> ellipf(z,m) + 0.5287219202206327872978255 + >>> quad(lambda t: (1-m*sin(t)**2)**(-0.5), [0,z]) + 0.5287219202206327872978255 + + The arguments may be complex numbers:: + + >>> ellipf(3j, 0.5) + (0.0 + 1.713602407841590234804143j) + >>> ellipf(3+4j, 5-6j) + (1.269131241950351323305741 - 0.3561052815014558335412538j) + >>> z,m = 2+3j, 1.25 + >>> k = 1011 + >>> ellipf(z+pi*k,m); ellipf(z,m) + 2*k*ellipk(m) + (4086.184383622179764082821 - 3003.003538923749396546871j) + (4086.184383622179764082821 - 3003.003538923749396546871j) + + For `|\Re(z)| < \pi/2`, the function can be expressed as a + hypergeometric series of two variables + (see :func:`~mpmath.appellf1`):: + + >>> z,m = 0.5, 0.25 + >>> ellipf(z,m) + 0.5050887275786480788831083 + >>> sin(z)*appellf1(0.5,0.5,0.5,1.5,sin(z)**2,m*sin(z)**2) + 0.5050887275786480788831083 + + """ + z = phi + if not (ctx.isnormal(z) and ctx.isnormal(m)): + if m == 0: + return z + m + if z == 0: + return z * m + if m == ctx.inf or m == ctx.ninf: return z/m + raise ValueError + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if m == 1: + if away: + return ctx.inf + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellipk(m) + else: + P = 0 + c, s = ctx.cos_sin(z) + return s * ctx.elliprf(c**2, 1-m*s**2, 1) + P + +@defun_wrapped +def ellipe(ctx, *args): + r""" + Called with a single argument `m`, evaluates the Legendre complete + elliptic integral of the second kind, `E(m)`, defined by + + .. math :: E(m) = \int_0^{\pi/2} \sqrt{1-m \sin^2 t} \, dt \,=\, + \frac{\pi}{2} + \,_2F_1\left(\frac{1}{2}, -\frac{1}{2}, 1, m\right). + + Called with two arguments `\phi, m`, evaluates the incomplete elliptic + integral of the second kind + + .. math :: + + E(\phi,m) = \int_0^{\phi} \sqrt{1-m \sin^2 t} \, dt = + \int_0^{\sin z} + \frac{\sqrt{1-mt^2}}{\sqrt{1-t^2}} \, dt. + + The incomplete integral reduces to a complete integral when + `\phi = \frac{\pi}{2}`; that is, + + .. math :: + + E\left(\frac{\pi}{2}, m\right) = E(m). + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(z) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + E(\phi + n \pi, m) = 2 n E(m) + E(\phi,m), n \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellipe.py + .. image :: /plots/ellipe.png + + **Examples for the complete integral** + + Basic values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> ellipe(0) + 1.570796326794896619231322 + >>> ellipe(1) + 1.0 + >>> ellipe(-1) + 1.910098894513856008952381 + >>> ellipe(2) + (0.5990701173677961037199612 + 0.5990701173677961037199612j) + >>> ellipe(inf) + (0.0 + +infj) + >>> ellipe(-inf) + +inf + + Verifying the defining integral and hypergeometric + representation:: + + >>> ellipe(0.5) + 1.350643881047675502520175 + >>> quad(lambda t: sqrt(1-0.5*sin(t)**2), [0, pi/2]) + 1.350643881047675502520175 + >>> pi/2*hyp2f1(0.5,-0.5,1,0.5) + 1.350643881047675502520175 + + Evaluation is supported for arbitrary complex `m`:: + + >>> ellipe(0.5+0.25j) + (1.360868682163129682716687 - 0.1238733442561786843557315j) + >>> ellipe(3+4j) + (1.499553520933346954333612 - 1.577879007912758274533309j) + + A definite integral:: + + >>> quad(ellipe, [0,1]) + 1.333333333333333333333333 + + **Examples for the incomplete integral** + + Basic values and limits:: + + >>> ellipe(0,1) + 0.0 + >>> ellipe(0,0) + 0.0 + >>> ellipe(1,0) + 1.0 + >>> ellipe(2+3j,0) + (2.0 + 3.0j) + >>> ellipe(1,1); sin(1) + 0.8414709848078965066525023 + 0.8414709848078965066525023 + >>> ellipe(pi/2, -0.5); ellipe(-0.5) + 1.751771275694817862026502 + 1.751771275694817862026502 + >>> ellipe(pi/2, 1); ellipe(-pi/2, 1) + 1.0 + -1.0 + >>> ellipe(1.5, 1) + 0.9974949866040544309417234 + + Comparing with numerical integration:: + + >>> z,m = 0.5, 1.25 + >>> ellipe(z,m) + 0.4740152182652628394264449 + >>> quad(lambda t: sqrt(1-m*sin(t)**2), [0,z]) + 0.4740152182652628394264449 + + The arguments may be complex numbers:: + + >>> ellipe(3j, 0.5) + (0.0 + 7.551991234890371873502105j) + >>> ellipe(3+4j, 5-6j) + (24.15299022574220502424466 + 75.2503670480325997418156j) + >>> k = 35 + >>> z,m = 2+3j, 1.25 + >>> ellipe(z+pi*k,m); ellipe(z,m) + 2*k*ellipe(m) + (48.30138799412005235090766 + 17.47255216721987688224357j) + (48.30138799412005235090766 + 17.47255216721987688224357j) + + For `|\Re(z)| < \pi/2`, the function can be expressed as a + hypergeometric series of two variables + (see :func:`~mpmath.appellf1`):: + + >>> z,m = 0.5, 0.25 + >>> ellipe(z,m) + 0.4950017030164151928870375 + >>> sin(z)*appellf1(0.5,0.5,-0.5,1.5,sin(z)**2,m*sin(z)**2) + 0.4950017030164151928870376 + + """ + if len(args) == 1: + return ctx._ellipe(args[0]) + else: + phi, m = args + z = phi + if not (ctx.isnormal(z) and ctx.isnormal(m)): + if m == 0: + return z + m + if z == 0: + return z * m + if m == ctx.inf or m == ctx.ninf: + return ctx.inf + raise ValueError + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellipe(m) + else: + P = 0 + def terms(): + c, s = ctx.cos_sin(z) + x = c**2 + y = 1-m*s**2 + RF = ctx.elliprf(x, y, 1) + RD = ctx.elliprd(x, y, 1) + return s*RF, -m*s**3*RD/3 + return ctx.sum_accurately(terms) + P + +@defun_wrapped +def ellippi(ctx, *args): + r""" + Called with three arguments `n, \phi, m`, evaluates the Legendre + incomplete elliptic integral of the third kind + + .. math :: + + \Pi(n; \phi, m) = \int_0^{\phi} + \frac{dt}{(1-n \sin^2 t) \sqrt{1-m \sin^2 t}} = + \int_0^{\sin \phi} + \frac{dt}{(1-nt^2) \sqrt{1-t^2} \sqrt{1-mt^2}}. + + Called with two arguments `n, m`, evaluates the complete + elliptic integral of the third kind + `\Pi(n,m) = \Pi(n; \frac{\pi}{2},m)`. + + In the defining integral, it is assumed that the principal branch + of the square root is taken and that the path of integration avoids + crossing any branch cuts. Outside `-\pi/2 \le \Re(\phi) \le \pi/2`, + the function extends quasi-periodically as + + .. math :: + + \Pi(n,\phi+k\pi,m) = 2k\Pi(n,m) + \Pi(n,\phi,m), k \in \mathbb{Z}. + + **Plots** + + .. literalinclude :: /plots/ellippi.py + .. image :: /plots/ellippi.png + + **Examples for the complete integral** + + Some basic values and limits:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> ellippi(0,-5); ellipk(-5) + 0.9555039270640439337379334 + 0.9555039270640439337379334 + >>> ellippi(inf,2) + 0.0 + >>> ellippi(2,inf) + 0.0 + >>> abs(ellippi(1,5)) + +inf + >>> abs(ellippi(0.25,1)) + +inf + + Evaluation in terms of simpler functions:: + + >>> ellippi(0.25,0.25); ellipe(0.25)/(1-0.25) + 1.956616279119236207279727 + 1.956616279119236207279727 + >>> ellippi(3,0); pi/(2*sqrt(-2)) + (0.0 - 1.11072073453959156175397j) + (0.0 - 1.11072073453959156175397j) + >>> ellippi(-3,0); pi/(2*sqrt(4)) + 0.7853981633974483096156609 + 0.7853981633974483096156609 + + **Examples for the incomplete integral** + + Basic values and limits:: + + >>> ellippi(0.25,-0.5); ellippi(0.25,pi/2,-0.5) + 1.622944760954741603710555 + 1.622944760954741603710555 + >>> ellippi(1,0,1) + 0.0 + >>> ellippi(inf,0,1) + 0.0 + >>> ellippi(0,0.25,0.5); ellipf(0.25,0.5) + 0.2513040086544925794134591 + 0.2513040086544925794134591 + >>> ellippi(1,1,1); (log(sec(1)+tan(1))+sec(1)*tan(1))/2 + 2.054332933256248668692452 + 2.054332933256248668692452 + >>> ellippi(0.25, 53*pi/2, 0.75); 53*ellippi(0.25,0.75) + 135.240868757890840755058 + 135.240868757890840755058 + >>> ellippi(0.5,pi/4,0.5); 2*ellipe(pi/4,0.5)-1/sqrt(3) + 0.9190227391656969903987269 + 0.9190227391656969903987269 + + Complex arguments are supported:: + + >>> ellippi(0.5, 5+6j-2*pi, -7-8j) + (-0.3612856620076747660410167 + 0.5217735339984807829755815j) + + Some degenerate cases:: + + >>> ellippi(1,1) + +inf + >>> ellippi(1,0) + +inf + >>> ellippi(1,2,0) + +inf + >>> ellippi(1,2,1) + +inf + >>> ellippi(1,0,1) + 0.0 + + """ + if len(args) == 2: + n, m = args + complete = True + z = phi = ctx.pi/2 + else: + n, phi, m = args + complete = False + z = phi + if not (ctx.isnormal(n) and ctx.isnormal(z) and ctx.isnormal(m)): + if ctx.isnan(n) or ctx.isnan(z) or ctx.isnan(m): + raise ValueError + if complete: + if m == 0: + if n == 1: + return ctx.inf + return ctx.pi/(2*ctx.sqrt(1-n)) + if n == 0: return ctx.ellipk(m) + if ctx.isinf(n) or ctx.isinf(m): return ctx.zero + else: + if z == 0: return z + if ctx.isinf(n): return ctx.zero + if ctx.isinf(m): return ctx.zero + if ctx.isinf(n) or ctx.isinf(z) or ctx.isinf(m): + raise ValueError + if complete: + if m == 1: + if n == 1: + return ctx.inf + return -ctx.inf/ctx.sign(n-1) + away = False + else: + x = z.real + ctx.prec += max(0, ctx.mag(x)) + pi = +ctx.pi + away = abs(x) > pi/2 + if away: + d = ctx.nint(x/pi) + z = z-pi*d + P = 2*d*ctx.ellippi(n,m) + if ctx.isinf(P): + return ctx.inf + else: + P = 0 + def terms(): + if complete: + c, s = ctx.zero, ctx.one + else: + c, s = ctx.cos_sin(z) + x = c**2 + y = 1-m*s**2 + RF = ctx.elliprf(x, y, 1) + RJ = ctx.elliprj(x, y, 1, 1-n*s**2) + return s*RF, n*s**3*RJ/3 + return ctx.sum_accurately(terms) + P diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/expintegrals.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/expintegrals.py new file mode 100644 index 0000000000000000000000000000000000000000..0dee8356c0386819d8f0421fded476ee77229359 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/expintegrals.py @@ -0,0 +1,425 @@ +from .functions import defun, defun_wrapped + +@defun_wrapped +def _erf_complex(ctx, z): + z2 = ctx.square_exp_arg(z, -1) + #z2 = -z**2 + v = (2/ctx.sqrt(ctx.pi))*z * ctx.hyp1f1((1,2),(3,2), z2) + if not ctx._re(z): + v = ctx._im(v)*ctx.j + return v + +@defun_wrapped +def _erfc_complex(ctx, z): + if ctx.re(z) > 2: + z2 = ctx.square_exp_arg(z) + nz2 = ctx.fneg(z2, exact=True) + v = ctx.exp(nz2)/ctx.sqrt(ctx.pi) * ctx.hyperu((1,2),(1,2), z2) + else: + v = 1 - ctx._erf_complex(z) + if not ctx._re(z): + v = 1+ctx._im(v)*ctx.j + return v + +@defun +def erf(ctx, z): + z = ctx.convert(z) + if ctx._is_real_type(z): + try: + return ctx._erf(z) + except NotImplementedError: + pass + if ctx._is_complex_type(z) and not z.imag: + try: + return type(z)(ctx._erf(z.real)) + except NotImplementedError: + pass + return ctx._erf_complex(z) + +@defun +def erfc(ctx, z): + z = ctx.convert(z) + if ctx._is_real_type(z): + try: + return ctx._erfc(z) + except NotImplementedError: + pass + if ctx._is_complex_type(z) and not z.imag: + try: + return type(z)(ctx._erfc(z.real)) + except NotImplementedError: + pass + return ctx._erfc_complex(z) + +@defun +def square_exp_arg(ctx, z, mult=1, reciprocal=False): + prec = ctx.prec*4+20 + if reciprocal: + z2 = ctx.fmul(z, z, prec=prec) + z2 = ctx.fdiv(ctx.one, z2, prec=prec) + else: + z2 = ctx.fmul(z, z, prec=prec) + if mult != 1: + z2 = ctx.fmul(z2, mult, exact=True) + return z2 + +@defun_wrapped +def erfi(ctx, z): + if not z: + return z + z2 = ctx.square_exp_arg(z) + v = (2/ctx.sqrt(ctx.pi)*z) * ctx.hyp1f1((1,2), (3,2), z2) + if not ctx._re(z): + v = ctx._im(v)*ctx.j + return v + +@defun_wrapped +def erfinv(ctx, x): + xre = ctx._re(x) + if (xre != x) or (xre < -1) or (xre > 1): + return ctx.bad_domain("erfinv(x) is defined only for -1 <= x <= 1") + x = xre + #if ctx.isnan(x): return x + if not x: return x + if x == 1: return ctx.inf + if x == -1: return ctx.ninf + if abs(x) < 0.9: + a = 0.53728*x**3 + 0.813198*x + else: + # An asymptotic formula + u = ctx.ln(2/ctx.pi/(abs(x)-1)**2) + a = ctx.sign(x) * ctx.sqrt(u - ctx.ln(u))/ctx.sqrt(2) + ctx.prec += 10 + return ctx.findroot(lambda t: ctx.erf(t)-x, a) + +@defun_wrapped +def npdf(ctx, x, mu=0, sigma=1): + sigma = ctx.convert(sigma) + return ctx.exp(-(x-mu)**2/(2*sigma**2)) / (sigma*ctx.sqrt(2*ctx.pi)) + +@defun_wrapped +def ncdf(ctx, x, mu=0, sigma=1): + a = (x-mu)/(sigma*ctx.sqrt(2)) + if a < 0: + return ctx.erfc(-a)/2 + else: + return (1+ctx.erf(a))/2 + +@defun_wrapped +def betainc(ctx, a, b, x1=0, x2=1, regularized=False): + if x1 == x2: + v = 0 + elif not x1: + if x1 == 0 and x2 == 1: + v = ctx.beta(a, b) + else: + v = x2**a * ctx.hyp2f1(a, 1-b, a+1, x2) / a + else: + m, d = ctx.nint_distance(a) + if m <= 0: + if d < -ctx.prec: + h = +ctx.eps + ctx.prec *= 2 + a += h + elif d < -4: + ctx.prec -= d + s1 = x2**a * ctx.hyp2f1(a,1-b,a+1,x2) + s2 = x1**a * ctx.hyp2f1(a,1-b,a+1,x1) + v = (s1 - s2) / a + if regularized: + v /= ctx.beta(a,b) + return v + +@defun +def gammainc(ctx, z, a=0, b=None, regularized=False): + regularized = bool(regularized) + z = ctx.convert(z) + if a is None: + a = ctx.zero + lower_modified = False + else: + a = ctx.convert(a) + lower_modified = a != ctx.zero + if b is None: + b = ctx.inf + upper_modified = False + else: + b = ctx.convert(b) + upper_modified = b != ctx.inf + # Complete gamma function + if not (upper_modified or lower_modified): + if regularized: + if ctx.re(z) < 0: + return ctx.inf + elif ctx.re(z) > 0: + return ctx.one + else: + return ctx.nan + return ctx.gamma(z) + if a == b: + return ctx.zero + # Standardize + if ctx.re(a) > ctx.re(b): + return -ctx.gammainc(z, b, a, regularized) + # Generalized gamma + if upper_modified and lower_modified: + return +ctx._gamma3(z, a, b, regularized) + # Upper gamma + elif lower_modified: + return ctx._upper_gamma(z, a, regularized) + # Lower gamma + elif upper_modified: + return ctx._lower_gamma(z, b, regularized) + +@defun +def _lower_gamma(ctx, z, b, regularized=False): + # Pole + if ctx.isnpint(z): + return type(z)(ctx.inf) + G = [z] * regularized + negb = ctx.fneg(b, exact=True) + def h(z): + T1 = [ctx.exp(negb), b, z], [1, z, -1], [], G, [1], [1+z], b + return (T1,) + return ctx.hypercomb(h, [z]) + +@defun +def _upper_gamma(ctx, z, a, regularized=False): + # Fast integer case, when available + if ctx.isint(z): + try: + if regularized: + # Gamma pole + if ctx.isnpint(z): + return type(z)(ctx.zero) + orig = ctx.prec + try: + ctx.prec += 10 + return ctx._gamma_upper_int(z, a) / ctx.gamma(z) + finally: + ctx.prec = orig + else: + return ctx._gamma_upper_int(z, a) + except NotImplementedError: + pass + # hypercomb is unable to detect the exact zeros, so handle them here + if z == 2 and a == -1: + return (z+a)*0 + if z == 3 and (a == -1-1j or a == -1+1j): + return (z+a)*0 + nega = ctx.fneg(a, exact=True) + G = [z] * regularized + # Use 2F0 series when possible; fall back to lower gamma representation + try: + def h(z): + r = z-1 + return [([ctx.exp(nega), a], [1, r], [], G, [1, -r], [], 1/nega)] + return ctx.hypercomb(h, [z], force_series=True) + except ctx.NoConvergence: + def h(z): + T1 = [], [1, z-1], [z], G, [], [], 0 + T2 = [-ctx.exp(nega), a, z], [1, z, -1], [], G, [1], [1+z], a + return T1, T2 + return ctx.hypercomb(h, [z]) + +@defun +def _gamma3(ctx, z, a, b, regularized=False): + pole = ctx.isnpint(z) + if regularized and pole: + return ctx.zero + try: + ctx.prec += 15 + # We don't know in advance whether it's better to write as a difference + # of lower or upper gamma functions, so try both + T1 = ctx.gammainc(z, a, regularized=regularized) + T2 = ctx.gammainc(z, b, regularized=regularized) + R = T1 - T2 + if ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10: + return R + if not pole: + T1 = ctx.gammainc(z, 0, b, regularized=regularized) + T2 = ctx.gammainc(z, 0, a, regularized=regularized) + R = T1 - T2 + # May be ok, but should probably at least print a warning + # about possible cancellation + if 1: #ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10: + return R + finally: + ctx.prec -= 15 + raise NotImplementedError + +@defun_wrapped +def expint(ctx, n, z): + if ctx.isint(n) and ctx._is_real_type(z): + try: + return ctx._expint_int(n, z) + except NotImplementedError: + pass + if ctx.isnan(n) or ctx.isnan(z): + return z*n + if z == ctx.inf: + return 1/z + if z == 0: + # integral from 1 to infinity of t^n + if ctx.re(n) <= 1: + # TODO: reasonable sign of infinity + return type(z)(ctx.inf) + else: + return ctx.one/(n-1) + if n == 0: + return ctx.exp(-z)/z + if n == -1: + return ctx.exp(-z)*(z+1)/z**2 + return z**(n-1) * ctx.gammainc(1-n, z) + +@defun_wrapped +def li(ctx, z, offset=False): + if offset: + if z == 2: + return ctx.zero + return ctx.ei(ctx.ln(z)) - ctx.ei(ctx.ln2) + if not z: + return z + if z == 1: + return ctx.ninf + return ctx.ei(ctx.ln(z)) + +@defun +def ei(ctx, z): + try: + return ctx._ei(z) + except NotImplementedError: + return ctx._ei_generic(z) + +@defun_wrapped +def _ei_generic(ctx, z): + # Note: the following is currently untested because mp and fp + # both use special-case ei code + if z == ctx.inf: + return z + if z == ctx.ninf: + return ctx.zero + if ctx.mag(z) > 1: + try: + r = ctx.one/z + v = ctx.exp(z)*ctx.hyper([1,1],[],r, + maxterms=ctx.prec, force_series=True)/z + im = ctx._im(z) + if im > 0: + v += ctx.pi*ctx.j + if im < 0: + v -= ctx.pi*ctx.j + return v + except ctx.NoConvergence: + pass + v = z*ctx.hyp2f2(1,1,2,2,z) + ctx.euler + if ctx._im(z): + v += 0.5*(ctx.log(z) - ctx.log(ctx.one/z)) + else: + v += ctx.log(abs(z)) + return v + +@defun +def e1(ctx, z): + try: + return ctx._e1(z) + except NotImplementedError: + return ctx.expint(1, z) + +@defun +def ci(ctx, z): + try: + return ctx._ci(z) + except NotImplementedError: + return ctx._ci_generic(z) + +@defun_wrapped +def _ci_generic(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: return ctx.zero + if z == ctx.ninf: return ctx.pi*1j + jz = ctx.fmul(ctx.j,z,exact=True) + njz = ctx.fneg(jz,exact=True) + v = 0.5*(ctx.ei(jz) + ctx.ei(njz)) + zreal = ctx._re(z) + zimag = ctx._im(z) + if zreal == 0: + if zimag > 0: v += ctx.pi*0.5j + if zimag < 0: v -= ctx.pi*0.5j + if zreal < 0: + if zimag >= 0: v += ctx.pi*1j + if zimag < 0: v -= ctx.pi*1j + if ctx._is_real_type(z) and zreal > 0: + v = ctx._re(v) + return v + +@defun +def si(ctx, z): + try: + return ctx._si(z) + except NotImplementedError: + return ctx._si_generic(z) + +@defun_wrapped +def _si_generic(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: return 0.5*ctx.pi + if z == ctx.ninf: return -0.5*ctx.pi + # Suffers from cancellation near 0 + if ctx.mag(z) >= -1: + jz = ctx.fmul(ctx.j,z,exact=True) + njz = ctx.fneg(jz,exact=True) + v = (-0.5j)*(ctx.ei(jz) - ctx.ei(njz)) + zreal = ctx._re(z) + if zreal > 0: + v -= 0.5*ctx.pi + if zreal < 0: + v += 0.5*ctx.pi + if ctx._is_real_type(z): + v = ctx._re(v) + return v + else: + return z*ctx.hyp1f2((1,2),(3,2),(3,2),-0.25*z*z) + +@defun_wrapped +def chi(ctx, z): + nz = ctx.fneg(z, exact=True) + v = 0.5*(ctx.ei(z) + ctx.ei(nz)) + zreal = ctx._re(z) + zimag = ctx._im(z) + if zimag > 0: + v += ctx.pi*0.5j + elif zimag < 0: + v -= ctx.pi*0.5j + elif zreal < 0: + v += ctx.pi*1j + return v + +@defun_wrapped +def shi(ctx, z): + # Suffers from cancellation near 0 + if ctx.mag(z) >= -1: + nz = ctx.fneg(z, exact=True) + v = 0.5*(ctx.ei(z) - ctx.ei(nz)) + zimag = ctx._im(z) + if zimag > 0: v -= 0.5j*ctx.pi + if zimag < 0: v += 0.5j*ctx.pi + return v + else: + return z * ctx.hyp1f2((1,2),(3,2),(3,2),0.25*z*z) + +@defun_wrapped +def fresnels(ctx, z): + if z == ctx.inf: + return ctx.mpf(0.5) + if z == ctx.ninf: + return ctx.mpf(-0.5) + return ctx.pi*z**3/6*ctx.hyp1f2((3,4),(3,2),(7,4),-ctx.pi**2*z**4/16) + +@defun_wrapped +def fresnelc(ctx, z): + if z == ctx.inf: + return ctx.mpf(0.5) + if z == ctx.ninf: + return ctx.mpf(-0.5) + return z*ctx.hyp1f2((1,4),(1,2),(5,4),-ctx.pi**2*z**4/16) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/factorials.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/factorials.py new file mode 100644 index 0000000000000000000000000000000000000000..9259e40b95bf1c908a7ad98b59bbb33528606b07 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/factorials.py @@ -0,0 +1,187 @@ +from ..libmp.backend import xrange +from .functions import defun, defun_wrapped + +@defun +def gammaprod(ctx, a, b, _infsign=False): + a = [ctx.convert(x) for x in a] + b = [ctx.convert(x) for x in b] + poles_num = [] + poles_den = [] + regular_num = [] + regular_den = [] + for x in a: [regular_num, poles_num][ctx.isnpint(x)].append(x) + for x in b: [regular_den, poles_den][ctx.isnpint(x)].append(x) + # One more pole in numerator or denominator gives 0 or inf + if len(poles_num) < len(poles_den): return ctx.zero + if len(poles_num) > len(poles_den): + # Get correct sign of infinity for x+h, h -> 0 from above + # XXX: hack, this should be done properly + if _infsign: + a = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_num] + b = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_den] + return ctx.sign(ctx.gammaprod(a+regular_num,b+regular_den)) * ctx.inf + else: + return ctx.inf + # All poles cancel + # lim G(i)/G(j) = (-1)**(i+j) * gamma(1-j) / gamma(1-i) + p = ctx.one + orig = ctx.prec + try: + ctx.prec = orig + 15 + while poles_num: + i = poles_num.pop() + j = poles_den.pop() + p *= (-1)**(i+j) * ctx.gamma(1-j) / ctx.gamma(1-i) + for x in regular_num: p *= ctx.gamma(x) + for x in regular_den: p /= ctx.gamma(x) + finally: + ctx.prec = orig + return +p + +@defun +def beta(ctx, x, y): + x = ctx.convert(x) + y = ctx.convert(y) + if ctx.isinf(y): + x, y = y, x + if ctx.isinf(x): + if x == ctx.inf and not ctx._im(y): + if y == ctx.ninf: + return ctx.nan + if y > 0: + return ctx.zero + if ctx.isint(y): + return ctx.nan + if y < 0: + return ctx.sign(ctx.gamma(y)) * ctx.inf + return ctx.nan + xy = ctx.fadd(x, y, prec=2*ctx.prec) + return ctx.gammaprod([x, y], [xy]) + +@defun +def binomial(ctx, n, k): + n1 = ctx.fadd(n, 1, prec=2*ctx.prec) + k1 = ctx.fadd(k, 1, prec=2*ctx.prec) + nk1 = ctx.fsub(n1, k, prec=2*ctx.prec) + return ctx.gammaprod([n1], [k1, nk1]) + +@defun +def rf(ctx, x, n): + xn = ctx.fadd(x, n, prec=2*ctx.prec) + return ctx.gammaprod([xn], [x]) + +@defun +def ff(ctx, x, n): + x1 = ctx.fadd(x, 1, prec=2*ctx.prec) + xn1 = ctx.fadd(ctx.fsub(x, n, prec=2*ctx.prec), 1, prec=2*ctx.prec) + return ctx.gammaprod([x1], [xn1]) + +@defun_wrapped +def fac2(ctx, x): + if ctx.isinf(x): + if x == ctx.inf: + return x + return ctx.nan + return 2**(x/2)*(ctx.pi/2)**((ctx.cospi(x)-1)/4)*ctx.gamma(x/2+1) + +@defun_wrapped +def barnesg(ctx, z): + if ctx.isinf(z): + if z == ctx.inf: + return z + return ctx.nan + if ctx.isnan(z): + return z + if (not ctx._im(z)) and ctx._re(z) <= 0 and ctx.isint(ctx._re(z)): + return z*0 + # Account for size (would not be needed if computing log(G)) + if abs(z) > 5: + ctx.dps += 2*ctx.log(abs(z),2) + # Reflection formula + if ctx.re(z) < -ctx.dps: + w = 1-z + pi2 = 2*ctx.pi + u = ctx.expjpi(2*w) + v = ctx.j*ctx.pi/12 - ctx.j*ctx.pi*w**2/2 + w*ctx.ln(1-u) - \ + ctx.j*ctx.polylog(2, u)/pi2 + v = ctx.barnesg(2-z)*ctx.exp(v)/pi2**w + if ctx._is_real_type(z): + v = ctx._re(v) + return v + # Estimate terms for asymptotic expansion + # TODO: fixme, obviously + N = ctx.dps // 2 + 5 + G = 1 + while abs(z) < N or ctx.re(z) < 1: + G /= ctx.gamma(z) + z += 1 + z -= 1 + s = ctx.mpf(1)/12 + s -= ctx.log(ctx.glaisher) + s += z*ctx.log(2*ctx.pi)/2 + s += (z**2/2-ctx.mpf(1)/12)*ctx.log(z) + s -= 3*z**2/4 + z2k = z2 = z**2 + for k in xrange(1, N+1): + t = ctx.bernoulli(2*k+2) / (4*k*(k+1)*z2k) + if abs(t) < ctx.eps: + #print k, N # check how many terms were needed + break + z2k *= z2 + s += t + #if k == N: + # print "warning: series for barnesg failed to converge", ctx.dps + return G*ctx.exp(s) + +@defun +def superfac(ctx, z): + return ctx.barnesg(z+2) + +@defun_wrapped +def hyperfac(ctx, z): + # XXX: estimate needed extra bits accurately + if z == ctx.inf: + return z + if abs(z) > 5: + extra = 4*int(ctx.log(abs(z),2)) + else: + extra = 0 + ctx.prec += extra + if not ctx._im(z) and ctx._re(z) < 0 and ctx.isint(ctx._re(z)): + n = int(ctx.re(z)) + h = ctx.hyperfac(-n-1) + if ((n+1)//2) & 1: + h = -h + if ctx._is_complex_type(z): + return h + 0j + return h + zp1 = z+1 + # Wrong branch cut + #v = ctx.gamma(zp1)**z + #ctx.prec -= extra + #return v / ctx.barnesg(zp1) + v = ctx.exp(z*ctx.loggamma(zp1)) + ctx.prec -= extra + return v / ctx.barnesg(zp1) + +''' +@defun +def psi0(ctx, z): + """Shortcut for psi(0,z) (the digamma function)""" + return ctx.psi(0, z) + +@defun +def psi1(ctx, z): + """Shortcut for psi(1,z) (the trigamma function)""" + return ctx.psi(1, z) + +@defun +def psi2(ctx, z): + """Shortcut for psi(2,z) (the tetragamma function)""" + return ctx.psi(2, z) + +@defun +def psi3(ctx, z): + """Shortcut for psi(3,z) (the pentagamma function)""" + return ctx.psi(3, z) +''' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/functions.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/functions.py new file mode 100644 index 0000000000000000000000000000000000000000..4cdf5dd921418db10847ea75b32f8e6dfacdba64 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/functions.py @@ -0,0 +1,645 @@ +from ..libmp.backend import xrange + +class SpecialFunctions(object): + """ + This class implements special functions using high-level code. + + Elementary and some other functions (e.g. gamma function, basecase + hypergeometric series) are assumed to be predefined by the context as + "builtins" or "low-level" functions. + """ + defined_functions = {} + + # The series for the Jacobi theta functions converge for |q| < 1; + # in the current implementation they throw a ValueError for + # abs(q) > THETA_Q_LIM + THETA_Q_LIM = 1 - 10**-7 + + def __init__(self): + cls = self.__class__ + for name in cls.defined_functions: + f, wrap = cls.defined_functions[name] + cls._wrap_specfun(name, f, wrap) + + self.mpq_1 = self._mpq((1,1)) + self.mpq_0 = self._mpq((0,1)) + self.mpq_1_2 = self._mpq((1,2)) + self.mpq_3_2 = self._mpq((3,2)) + self.mpq_1_4 = self._mpq((1,4)) + self.mpq_1_16 = self._mpq((1,16)) + self.mpq_3_16 = self._mpq((3,16)) + self.mpq_5_2 = self._mpq((5,2)) + self.mpq_3_4 = self._mpq((3,4)) + self.mpq_7_4 = self._mpq((7,4)) + self.mpq_5_4 = self._mpq((5,4)) + self.mpq_1_3 = self._mpq((1,3)) + self.mpq_2_3 = self._mpq((2,3)) + self.mpq_4_3 = self._mpq((4,3)) + self.mpq_1_6 = self._mpq((1,6)) + self.mpq_5_6 = self._mpq((5,6)) + self.mpq_5_3 = self._mpq((5,3)) + + self._misc_const_cache = {} + + self._aliases.update({ + 'phase' : 'arg', + 'conjugate' : 'conj', + 'nthroot' : 'root', + 'polygamma' : 'psi', + 'hurwitz' : 'zeta', + #'digamma' : 'psi0', + #'trigamma' : 'psi1', + #'tetragamma' : 'psi2', + #'pentagamma' : 'psi3', + 'fibonacci' : 'fib', + 'factorial' : 'fac', + }) + + self.zetazero_memoized = self.memoize(self.zetazero) + + # Default -- do nothing + @classmethod + def _wrap_specfun(cls, name, f, wrap): + setattr(cls, name, f) + + # Optional fast versions of common functions in common cases. + # If not overridden, default (generic hypergeometric series) + # implementations will be used + def _besselj(ctx, n, z): raise NotImplementedError + def _erf(ctx, z): raise NotImplementedError + def _erfc(ctx, z): raise NotImplementedError + def _gamma_upper_int(ctx, z, a): raise NotImplementedError + def _expint_int(ctx, n, z): raise NotImplementedError + def _zeta(ctx, s): raise NotImplementedError + def _zetasum_fast(ctx, s, a, n, derivatives, reflect): raise NotImplementedError + def _ei(ctx, z): raise NotImplementedError + def _e1(ctx, z): raise NotImplementedError + def _ci(ctx, z): raise NotImplementedError + def _si(ctx, z): raise NotImplementedError + def _altzeta(ctx, s): raise NotImplementedError + +def defun_wrapped(f): + SpecialFunctions.defined_functions[f.__name__] = f, True + return f + +def defun(f): + SpecialFunctions.defined_functions[f.__name__] = f, False + return f + +def defun_static(f): + setattr(SpecialFunctions, f.__name__, f) + return f + +@defun_wrapped +def cot(ctx, z): return ctx.one / ctx.tan(z) + +@defun_wrapped +def sec(ctx, z): return ctx.one / ctx.cos(z) + +@defun_wrapped +def csc(ctx, z): return ctx.one / ctx.sin(z) + +@defun_wrapped +def coth(ctx, z): return ctx.one / ctx.tanh(z) + +@defun_wrapped +def sech(ctx, z): return ctx.one / ctx.cosh(z) + +@defun_wrapped +def csch(ctx, z): return ctx.one / ctx.sinh(z) + +@defun_wrapped +def acot(ctx, z): + if not z: + return ctx.pi * 0.5 + else: + return ctx.atan(ctx.one / z) + +@defun_wrapped +def asec(ctx, z): return ctx.acos(ctx.one / z) + +@defun_wrapped +def acsc(ctx, z): return ctx.asin(ctx.one / z) + +@defun_wrapped +def acoth(ctx, z): + if not z: + return ctx.pi * 0.5j + else: + return ctx.atanh(ctx.one / z) + + +@defun_wrapped +def asech(ctx, z): return ctx.acosh(ctx.one / z) + +@defun_wrapped +def acsch(ctx, z): return ctx.asinh(ctx.one / z) + +@defun +def sign(ctx, x): + x = ctx.convert(x) + if not x or ctx.isnan(x): + return x + if ctx._is_real_type(x): + if x > 0: + return ctx.one + else: + return -ctx.one + return x / abs(x) + +@defun +def agm(ctx, a, b=1): + if b == 1: + return ctx.agm1(a) + a = ctx.convert(a) + b = ctx.convert(b) + return ctx._agm(a, b) + +@defun_wrapped +def sinc(ctx, x): + if ctx.isinf(x): + return 1/x + if not x: + return x+1 + return ctx.sin(x)/x + +@defun_wrapped +def sincpi(ctx, x): + if ctx.isinf(x): + return 1/x + if not x: + return x+1 + return ctx.sinpi(x)/(ctx.pi*x) + +# TODO: tests; improve implementation +@defun_wrapped +def expm1(ctx, x): + if not x: + return ctx.zero + # exp(x) - 1 ~ x + if ctx.mag(x) < -ctx.prec: + return x + 0.5*x**2 + # TODO: accurately eval the smaller of the real/imag parts + return ctx.sum_accurately(lambda: iter([ctx.exp(x),-1]),1) + +@defun_wrapped +def log1p(ctx, x): + if not x: + return ctx.zero + if ctx.mag(x) < -ctx.prec: + return x - 0.5*x**2 + return ctx.log(ctx.fadd(1, x, prec=2*ctx.prec)) + +@defun_wrapped +def powm1(ctx, x, y): + mag = ctx.mag + one = ctx.one + w = x**y - one + M = mag(w) + # Only moderate cancellation + if M > -8: + return w + # Check for the only possible exact cases + if not w: + if (not y) or (x in (1, -1, 1j, -1j) and ctx.isint(y)): + return w + x1 = x - one + magy = mag(y) + lnx = ctx.ln(x) + # Small y: x^y - 1 ~ log(x)*y + O(log(x)^2 * y^2) + if magy + mag(lnx) < -ctx.prec: + return lnx*y + (lnx*y)**2/2 + # TODO: accurately eval the smaller of the real/imag part + return ctx.sum_accurately(lambda: iter([x**y, -1]), 1) + +@defun +def _rootof1(ctx, k, n): + k = int(k) + n = int(n) + k %= n + if not k: + return ctx.one + elif 2*k == n: + return -ctx.one + elif 4*k == n: + return ctx.j + elif 4*k == 3*n: + return -ctx.j + return ctx.expjpi(2*ctx.mpf(k)/n) + +@defun +def root(ctx, x, n, k=0): + n = int(n) + x = ctx.convert(x) + if k: + # Special case: there is an exact real root + if (n & 1 and 2*k == n-1) and (not ctx.im(x)) and (ctx.re(x) < 0): + return -ctx.root(-x, n) + # Multiply by root of unity + prec = ctx.prec + try: + ctx.prec += 10 + v = ctx.root(x, n, 0) * ctx._rootof1(k, n) + finally: + ctx.prec = prec + return +v + return ctx._nthroot(x, n) + +@defun +def unitroots(ctx, n, primitive=False): + gcd = ctx._gcd + prec = ctx.prec + try: + ctx.prec += 10 + if primitive: + v = [ctx._rootof1(k,n) for k in range(n) if gcd(k,n) == 1] + else: + # TODO: this can be done *much* faster + v = [ctx._rootof1(k,n) for k in range(n)] + finally: + ctx.prec = prec + return [+x for x in v] + +@defun +def arg(ctx, x): + x = ctx.convert(x) + re = ctx._re(x) + im = ctx._im(x) + return ctx.atan2(im, re) + +@defun +def fabs(ctx, x): + return abs(ctx.convert(x)) + +@defun +def re(ctx, x): + x = ctx.convert(x) + if hasattr(x, "real"): # py2.5 doesn't have .real/.imag for all numbers + return x.real + return x + +@defun +def im(ctx, x): + x = ctx.convert(x) + if hasattr(x, "imag"): # py2.5 doesn't have .real/.imag for all numbers + return x.imag + return ctx.zero + +@defun +def conj(ctx, x): + x = ctx.convert(x) + try: + return x.conjugate() + except AttributeError: + return x + +@defun +def polar(ctx, z): + return (ctx.fabs(z), ctx.arg(z)) + +@defun_wrapped +def rect(ctx, r, phi): + return r * ctx.mpc(*ctx.cos_sin(phi)) + +@defun +def log(ctx, x, b=None): + if b is None: + return ctx.ln(x) + wp = ctx.prec + 20 + return ctx.ln(x, prec=wp) / ctx.ln(b, prec=wp) + +@defun +def log10(ctx, x): + return ctx.log(x, 10) + +@defun +def fmod(ctx, x, y): + return ctx.convert(x) % ctx.convert(y) + +@defun +def degrees(ctx, x): + return x / ctx.degree + +@defun +def radians(ctx, x): + return x * ctx.degree + +def _lambertw_special(ctx, z, k): + # W(0,0) = 0; all other branches are singular + if not z: + if not k: + return z + return ctx.ninf + z + if z == ctx.inf: + if k == 0: + return z + else: + return z + 2*k*ctx.pi*ctx.j + if z == ctx.ninf: + return (-z) + (2*k+1)*ctx.pi*ctx.j + # Some kind of nan or complex inf/nan? + return ctx.ln(z) + +import math +import cmath + +def _lambertw_approx_hybrid(z, k): + imag_sign = 0 + if hasattr(z, "imag"): + x = float(z.real) + y = z.imag + if y: + imag_sign = (-1) ** (y < 0) + y = float(y) + else: + x = float(z) + y = 0.0 + imag_sign = 0 + # hack to work regardless of whether Python supports -0.0 + if not y: + y = 0.0 + z = complex(x,y) + if k == 0: + if -4.0 < y < 4.0 and -1.0 < x < 2.5: + if imag_sign: + # Taylor series in upper/lower half-plane + if y > 1.00: return (0.876+0.645j) + (0.118-0.174j)*(z-(0.75+2.5j)) + if y > 0.25: return (0.505+0.204j) + (0.375-0.132j)*(z-(0.75+0.5j)) + if y < -1.00: return (0.876-0.645j) + (0.118+0.174j)*(z-(0.75-2.5j)) + if y < -0.25: return (0.505-0.204j) + (0.375+0.132j)*(z-(0.75-0.5j)) + # Taylor series near -1 + if x < -0.5: + if imag_sign >= 0: + return (-0.318+1.34j) + (-0.697-0.593j)*(z+1) + else: + return (-0.318-1.34j) + (-0.697+0.593j)*(z+1) + # return real type + r = -0.367879441171442 + if (not imag_sign) and x > r: + z = x + # Singularity near -1/e + if x < -0.2: + return -1 + 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r) + # Taylor series near 0 + if x < 0.5: return z + # Simple linear approximation + return 0.2 + 0.3*z + if (not imag_sign) and x > 0.0: + L1 = math.log(x); L2 = math.log(L1) + else: + L1 = cmath.log(z); L2 = cmath.log(L1) + elif k == -1: + # return real type + r = -0.367879441171442 + if (not imag_sign) and r < x < 0.0: + z = x + if (imag_sign >= 0) and y < 0.1 and -0.6 < x < -0.2: + return -1 - 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r) + if (not imag_sign) and -0.2 <= x < 0.0: + L1 = math.log(-x) + return L1 - math.log(-L1) + else: + if imag_sign == -1 and (not y) and x < 0.0: + L1 = cmath.log(z) - 3.1415926535897932j + else: + L1 = cmath.log(z) - 6.2831853071795865j + L2 = cmath.log(L1) + return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2) + +def _lambertw_series(ctx, z, k, tol): + """ + Return rough approximation for W_k(z) from an asymptotic series, + sufficiently accurate for the Halley iteration to converge to + the correct value. + """ + magz = ctx.mag(z) + if (-10 < magz < 900) and (-1000 < k < 1000): + # Near the branch point at -1/e + if magz < 1 and abs(z+0.36787944117144) < 0.05: + if k == 0 or (k == -1 and ctx._im(z) >= 0) or \ + (k == 1 and ctx._im(z) < 0): + delta = ctx.sum_accurately(lambda: [z, ctx.exp(-1)]) + cancellation = -ctx.mag(delta) + ctx.prec += cancellation + # Use series given in Corless et al. + p = ctx.sqrt(2*(ctx.e*z+1)) + ctx.prec -= cancellation + u = {0:ctx.mpf(-1), 1:ctx.mpf(1)} + a = {0:ctx.mpf(2), 1:ctx.mpf(-1)} + if k != 0: + p = -p + s = ctx.zero + # The series converges, so we could use it directly, but unless + # *extremely* close, it is better to just use the first few + # terms to get a good approximation for the iteration + for l in xrange(max(2,cancellation)): + if l not in u: + a[l] = ctx.fsum(u[j]*u[l+1-j] for j in xrange(2,l)) + u[l] = (l-1)*(u[l-2]/2+a[l-2]/4)/(l+1)-a[l]/2-u[l-1]/(l+1) + term = u[l] * p**l + s += term + if ctx.mag(term) < -tol: + return s, True + l += 1 + ctx.prec += cancellation//2 + return s, False + if k == 0 or k == -1: + return _lambertw_approx_hybrid(z, k), False + if k == 0: + if magz < -1: + return z*(1-z), False + L1 = ctx.ln(z) + L2 = ctx.ln(L1) + elif k == -1 and (not ctx._im(z)) and (-0.36787944117144 < ctx._re(z) < 0): + L1 = ctx.ln(-z) + return L1 - ctx.ln(-L1), False + else: + # This holds both as z -> 0 and z -> inf. + # Relative error is O(1/log(z)). + L1 = ctx.ln(z) + 2j*ctx.pi*k + L2 = ctx.ln(L1) + return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2), False + +@defun +def lambertw(ctx, z, k=0): + z = ctx.convert(z) + k = int(k) + if not ctx.isnormal(z): + return _lambertw_special(ctx, z, k) + prec = ctx.prec + ctx.prec += 20 + ctx.mag(k or 1) + wp = ctx.prec + tol = wp - 5 + w, done = _lambertw_series(ctx, z, k, tol) + if not done: + # Use Halley iteration to solve w*exp(w) = z + two = ctx.mpf(2) + for i in xrange(100): + ew = ctx.exp(w) + wew = w*ew + wewz = wew-z + wn = w - wewz/(wew+ew-(w+two)*wewz/(two*w+two)) + if ctx.mag(wn-w) <= ctx.mag(wn) - tol: + w = wn + break + else: + w = wn + if i == 100: + ctx.warn("Lambert W iteration failed to converge for z = %s" % z) + ctx.prec = prec + return +w + +@defun_wrapped +def bell(ctx, n, x=1): + x = ctx.convert(x) + if not n: + if ctx.isnan(x): + return x + return type(x)(1) + if ctx.isinf(x) or ctx.isinf(n) or ctx.isnan(x) or ctx.isnan(n): + return x**n + if n == 1: return x + if n == 2: return x*(x+1) + if x == 0: return ctx.sincpi(n) + return _polyexp(ctx, n, x, True) / ctx.exp(x) + +def _polyexp(ctx, n, x, extra=False): + def _terms(): + if extra: + yield ctx.sincpi(n) + t = x + k = 1 + while 1: + yield k**n * t + k += 1 + t = t*x/k + return ctx.sum_accurately(_terms, check_step=4) + +@defun_wrapped +def polyexp(ctx, s, z): + if ctx.isinf(z) or ctx.isinf(s) or ctx.isnan(z) or ctx.isnan(s): + return z**s + if z == 0: return z*s + if s == 0: return ctx.expm1(z) + if s == 1: return ctx.exp(z)*z + if s == 2: return ctx.exp(z)*z*(z+1) + return _polyexp(ctx, s, z) + +@defun_wrapped +def cyclotomic(ctx, n, z): + n = int(n) + if n < 0: + raise ValueError("n cannot be negative") + p = ctx.one + if n == 0: + return p + if n == 1: + return z - p + if n == 2: + return z + p + # Use divisor product representation. Unfortunately, this sometimes + # includes singularities for roots of unity, which we have to cancel out. + # Matching zeros/poles pairwise, we have (1-z^a)/(1-z^b) ~ a/b + O(z-1). + a_prod = 1 + b_prod = 1 + num_zeros = 0 + num_poles = 0 + for d in range(1,n+1): + if not n % d: + w = ctx.moebius(n//d) + # Use powm1 because it is important that we get 0 only + # if it really is exactly 0 + b = -ctx.powm1(z, d) + if b: + p *= b**w + else: + if w == 1: + a_prod *= d + num_zeros += 1 + elif w == -1: + b_prod *= d + num_poles += 1 + #print n, num_zeros, num_poles + if num_zeros: + if num_zeros > num_poles: + p *= 0 + else: + p *= a_prod + p /= b_prod + return p + +@defun +def mangoldt(ctx, n): + r""" + Evaluates the von Mangoldt function `\Lambda(n) = \log p` + if `n = p^k` a power of a prime, and `\Lambda(n) = 0` otherwise. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> [mangoldt(n) for n in range(-2,3)] + [0.0, 0.0, 0.0, 0.0, 0.6931471805599453094172321] + >>> mangoldt(6) + 0.0 + >>> mangoldt(7) + 1.945910149055313305105353 + >>> mangoldt(8) + 0.6931471805599453094172321 + >>> fsum(mangoldt(n) for n in range(101)) + 94.04531122935739224600493 + >>> fsum(mangoldt(n) for n in range(10001)) + 10013.39669326311478372032 + + """ + n = int(n) + if n < 2: + return ctx.zero + if n % 2 == 0: + # Must be a power of two + if n & (n-1) == 0: + return +ctx.ln2 + else: + return ctx.zero + # TODO: the following could be generalized into a perfect + # power testing function + # --- + # Look for a small factor + for p in (3,5,7,11,13,17,19,23,29,31): + if not n % p: + q, r = n // p, 0 + while q > 1: + q, r = divmod(q, p) + if r: + return ctx.zero + return ctx.ln(p) + if ctx.isprime(n): + return ctx.ln(n) + # Obviously, we could use arbitrary-precision arithmetic for this... + if n > 10**30: + raise NotImplementedError + k = 2 + while 1: + p = int(n**(1./k) + 0.5) + if p < 2: + return ctx.zero + if p ** k == n: + if ctx.isprime(p): + return ctx.ln(p) + k += 1 + +@defun +def stirling1(ctx, n, k, exact=False): + v = ctx._stirling1(int(n), int(k)) + if exact: + return int(v) + else: + return ctx.mpf(v) + +@defun +def stirling2(ctx, n, k, exact=False): + v = ctx._stirling2(int(n), int(k)) + if exact: + return int(v) + else: + return ctx.mpf(v) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/hypergeometric.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/hypergeometric.py new file mode 100644 index 0000000000000000000000000000000000000000..ddb50cbf3ea6daa5982678d3c26157a67a7d7945 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/hypergeometric.py @@ -0,0 +1,1413 @@ +from ..libmp.backend import xrange +from .functions import defun, defun_wrapped + +def _check_need_perturb(ctx, terms, prec, discard_known_zeros): + perturb = recompute = False + extraprec = 0 + discard = [] + for term_index, term in enumerate(terms): + w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term + have_singular_nongamma_weight = False + # Avoid division by zero in leading factors (TODO: + # also check for near division by zero?) + for k, w in enumerate(w_s): + if not w: + if ctx.re(c_s[k]) <= 0 and c_s[k]: + perturb = recompute = True + have_singular_nongamma_weight = True + pole_count = [0, 0, 0] + # Check for gamma and series poles and near-poles + for data_index, data in enumerate([alpha_s, beta_s, b_s]): + for i, x in enumerate(data): + n, d = ctx.nint_distance(x) + # Poles + if n > 0: + continue + if d == ctx.ninf: + # OK if we have a polynomial + # ------------------------------ + ok = False + if data_index == 2: + for u in a_s: + if ctx.isnpint(u) and u >= int(n): + ok = True + break + if ok: + continue + pole_count[data_index] += 1 + # ------------------------------ + #perturb = recompute = True + #return perturb, recompute, extraprec + elif d < -4: + extraprec += -d + recompute = True + if discard_known_zeros and pole_count[1] > pole_count[0] + pole_count[2] \ + and not have_singular_nongamma_weight: + discard.append(term_index) + elif sum(pole_count): + perturb = recompute = True + return perturb, recompute, extraprec, discard + +_hypercomb_msg = """ +hypercomb() failed to converge to the requested %i bits of accuracy +using a working precision of %i bits. The function value may be zero or +infinite; try passing zeroprec=N or infprec=M to bound finite values between +2^(-N) and 2^M. Otherwise try a higher maxprec or maxterms. +""" + +@defun +def hypercomb(ctx, function, params=[], discard_known_zeros=True, **kwargs): + orig = ctx.prec + sumvalue = ctx.zero + dist = ctx.nint_distance + ninf = ctx.ninf + orig_params = params[:] + verbose = kwargs.get('verbose', False) + maxprec = kwargs.get('maxprec', ctx._default_hyper_maxprec(orig)) + kwargs['maxprec'] = maxprec # For calls to hypsum + zeroprec = kwargs.get('zeroprec') + infprec = kwargs.get('infprec') + perturbed_reference_value = None + hextra = 0 + try: + while 1: + ctx.prec += 10 + if ctx.prec > maxprec: + raise ValueError(_hypercomb_msg % (orig, ctx.prec)) + orig2 = ctx.prec + params = orig_params[:] + terms = function(*params) + if verbose: + print() + print("ENTERING hypercomb main loop") + print("prec =", ctx.prec) + print("hextra", hextra) + perturb, recompute, extraprec, discard = \ + _check_need_perturb(ctx, terms, orig, discard_known_zeros) + ctx.prec += extraprec + if perturb: + if "hmag" in kwargs: + hmag = kwargs["hmag"] + elif ctx._fixed_precision: + hmag = int(ctx.prec*0.3) + else: + hmag = orig + 10 + hextra + h = ctx.ldexp(ctx.one, -hmag) + ctx.prec = orig2 + 10 + hmag + 10 + for k in range(len(params)): + params[k] += h + # Heuristically ensure that the perturbations + # are "independent" so that two perturbations + # don't accidentally cancel each other out + # in a subtraction. + h += h/(k+1) + if recompute: + terms = function(*params) + if discard_known_zeros: + terms = [term for (i, term) in enumerate(terms) if i not in discard] + if not terms: + return ctx.zero + evaluated_terms = [] + for term_index, term_data in enumerate(terms): + w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term_data + if verbose: + print() + print(" Evaluating term %i/%i : %iF%i" % \ + (term_index+1, len(terms), len(a_s), len(b_s))) + print(" powers", ctx.nstr(w_s), ctx.nstr(c_s)) + print(" gamma", ctx.nstr(alpha_s), ctx.nstr(beta_s)) + print(" hyper", ctx.nstr(a_s), ctx.nstr(b_s)) + print(" z", ctx.nstr(z)) + #v = ctx.hyper(a_s, b_s, z, **kwargs) + #for a in alpha_s: v *= ctx.gamma(a) + #for b in beta_s: v *= ctx.rgamma(b) + #for w, c in zip(w_s, c_s): v *= ctx.power(w, c) + v = ctx.fprod([ctx.hyper(a_s, b_s, z, **kwargs)] + \ + [ctx.gamma(a) for a in alpha_s] + \ + [ctx.rgamma(b) for b in beta_s] + \ + [ctx.power(w,c) for (w,c) in zip(w_s,c_s)]) + if verbose: + print(" Value:", v) + evaluated_terms.append(v) + + if len(terms) == 1 and (not perturb): + sumvalue = evaluated_terms[0] + break + + if ctx._fixed_precision: + sumvalue = ctx.fsum(evaluated_terms) + break + + sumvalue = ctx.fsum(evaluated_terms) + term_magnitudes = [ctx.mag(x) for x in evaluated_terms] + max_magnitude = max(term_magnitudes) + sum_magnitude = ctx.mag(sumvalue) + cancellation = max_magnitude - sum_magnitude + if verbose: + print() + print(" Cancellation:", cancellation, "bits") + print(" Increased precision:", ctx.prec - orig, "bits") + + precision_ok = cancellation < ctx.prec - orig + + if zeroprec is None: + zero_ok = False + else: + zero_ok = max_magnitude - ctx.prec < -zeroprec + if infprec is None: + inf_ok = False + else: + inf_ok = max_magnitude > infprec + + if precision_ok and (not perturb) or ctx.isnan(cancellation): + break + elif precision_ok: + if perturbed_reference_value is None: + hextra += 20 + perturbed_reference_value = sumvalue + continue + elif ctx.mag(sumvalue - perturbed_reference_value) <= \ + ctx.mag(sumvalue) - orig: + break + elif zero_ok: + sumvalue = ctx.zero + break + elif inf_ok: + sumvalue = ctx.inf + break + elif 'hmag' in kwargs: + break + else: + hextra *= 2 + perturbed_reference_value = sumvalue + # Increase precision + else: + increment = min(max(cancellation, orig//2), max(extraprec,orig)) + ctx.prec += increment + if verbose: + print(" Must start over with increased precision") + continue + finally: + ctx.prec = orig + return +sumvalue + +@defun +def hyper(ctx, a_s, b_s, z, **kwargs): + """ + Hypergeometric function, general case. + """ + z = ctx.convert(z) + p = len(a_s) + q = len(b_s) + a_s = [ctx._convert_param(a) for a in a_s] + b_s = [ctx._convert_param(b) for b in b_s] + # Reduce degree by eliminating common parameters + if kwargs.get('eliminate', True): + elim_nonpositive = kwargs.get('eliminate_all', False) + i = 0 + while i < q and a_s: + b = b_s[i] + if b in a_s and (elim_nonpositive or not ctx.isnpint(b[0])): + a_s.remove(b) + b_s.remove(b) + p -= 1 + q -= 1 + else: + i += 1 + # Handle special cases + if p == 0: + if q == 1: return ctx._hyp0f1(b_s, z, **kwargs) + elif q == 0: return ctx.exp(z) + elif p == 1: + if q == 1: return ctx._hyp1f1(a_s, b_s, z, **kwargs) + elif q == 2: return ctx._hyp1f2(a_s, b_s, z, **kwargs) + elif q == 0: return ctx._hyp1f0(a_s[0][0], z) + elif p == 2: + if q == 1: return ctx._hyp2f1(a_s, b_s, z, **kwargs) + elif q == 2: return ctx._hyp2f2(a_s, b_s, z, **kwargs) + elif q == 3: return ctx._hyp2f3(a_s, b_s, z, **kwargs) + elif q == 0: return ctx._hyp2f0(a_s, b_s, z, **kwargs) + elif p == q+1: + return ctx._hypq1fq(p, q, a_s, b_s, z, **kwargs) + elif p > q+1 and not kwargs.get('force_series'): + return ctx._hyp_borel(p, q, a_s, b_s, z, **kwargs) + coeffs, types = zip(*(a_s+b_s)) + return ctx.hypsum(p, q, types, coeffs, z, **kwargs) + +@defun +def hyp0f1(ctx,b,z,**kwargs): + return ctx.hyper([],[b],z,**kwargs) + +@defun +def hyp1f1(ctx,a,b,z,**kwargs): + return ctx.hyper([a],[b],z,**kwargs) + +@defun +def hyp1f2(ctx,a1,b1,b2,z,**kwargs): + return ctx.hyper([a1],[b1,b2],z,**kwargs) + +@defun +def hyp2f1(ctx,a,b,c,z,**kwargs): + return ctx.hyper([a,b],[c],z,**kwargs) + +@defun +def hyp2f2(ctx,a1,a2,b1,b2,z,**kwargs): + return ctx.hyper([a1,a2],[b1,b2],z,**kwargs) + +@defun +def hyp2f3(ctx,a1,a2,b1,b2,b3,z,**kwargs): + return ctx.hyper([a1,a2],[b1,b2,b3],z,**kwargs) + +@defun +def hyp2f0(ctx,a,b,z,**kwargs): + return ctx.hyper([a,b],[],z,**kwargs) + +@defun +def hyp3f2(ctx,a1,a2,a3,b1,b2,z,**kwargs): + return ctx.hyper([a1,a2,a3],[b1,b2],z,**kwargs) + +@defun_wrapped +def _hyp1f0(ctx, a, z): + return (1-z) ** (-a) + +@defun +def _hyp0f1(ctx, b_s, z, **kwargs): + (b, btype), = b_s + if z: + magz = ctx.mag(z) + else: + magz = 0 + if magz >= 8 and not kwargs.get('force_series'): + try: + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric0F1/06/02/03/0004/ + # TODO: handle the all-real case more efficiently! + # TODO: figure out how much precision is needed (exponential growth) + orig = ctx.prec + try: + ctx.prec += 12 + magz//2 + def h(): + w = ctx.sqrt(-z) + jw = ctx.j*w + u = 1/(4*jw) + c = ctx.mpq_1_2 - b + E = ctx.exp(2*jw) + T1 = ([-jw,E], [c,-1], [], [], [b-ctx.mpq_1_2, ctx.mpq_3_2-b], [], -u) + T2 = ([jw,E], [c,1], [], [], [b-ctx.mpq_1_2, ctx.mpq_3_2-b], [], u) + return T1, T2 + v = ctx.hypercomb(h, [], force_series=True) + v = ctx.gamma(b)/(2*ctx.sqrt(ctx.pi))*v + finally: + ctx.prec = orig + if ctx._is_real_type(b) and ctx._is_real_type(z): + v = ctx._re(v) + return +v + except ctx.NoConvergence: + pass + return ctx.hypsum(0, 1, (btype,), [b], z, **kwargs) + +@defun +def _hyp1f1(ctx, a_s, b_s, z, **kwargs): + (a, atype), = a_s + (b, btype), = b_s + if not z: + return ctx.one+z + magz = ctx.mag(z) + if magz >= 7 and not (ctx.isint(a) and ctx.re(a) <= 0): + if ctx.isinf(z): + if ctx.sign(a) == ctx.sign(b) == ctx.sign(z) == 1: + return ctx.inf + return ctx.nan * z + try: + try: + ctx.prec += magz + sector = ctx._im(z) < 0 + def h(a,b): + if sector: + E = ctx.expjpi(ctx.fneg(a, exact=True)) + else: + E = ctx.expjpi(a) + rz = 1/z + T1 = ([E,z], [1,-a], [b], [b-a], [a, 1+a-b], [], -rz) + T2 = ([ctx.exp(z),z], [1,a-b], [b], [a], [b-a, 1-a], [], rz) + return T1, T2 + v = ctx.hypercomb(h, [a,b], force_series=True) + if ctx._is_real_type(a) and ctx._is_real_type(b) and ctx._is_real_type(z): + v = ctx._re(v) + return +v + except ctx.NoConvergence: + pass + finally: + ctx.prec -= magz + v = ctx.hypsum(1, 1, (atype, btype), [a, b], z, **kwargs) + return v + +def _hyp2f1_gosper(ctx,a,b,c,z,**kwargs): + # Use Gosper's recurrence + # See http://www.math.utexas.edu/pipermail/maxima/2006/000126.html + _a,_b,_c,_z = a, b, c, z + orig = ctx.prec + maxprec = kwargs.get('maxprec', 100*orig) + extra = 10 + while 1: + ctx.prec = orig + extra + #a = ctx.convert(_a) + #b = ctx.convert(_b) + #c = ctx.convert(_c) + z = ctx.convert(_z) + d = ctx.mpf(0) + e = ctx.mpf(1) + f = ctx.mpf(0) + k = 0 + # Common subexpression elimination, unfortunately making + # things a bit unreadable. The formula is quite messy to begin + # with, though... + abz = a*b*z + ch = c * ctx.mpq_1_2 + c1h = (c+1) * ctx.mpq_1_2 + nz = 1-z + g = z/nz + abg = a*b*g + cba = c-b-a + z2 = z-2 + tol = -ctx.prec - 10 + nstr = ctx.nstr + nprint = ctx.nprint + mag = ctx.mag + maxmag = ctx.ninf + while 1: + kch = k+ch + kakbz = (k+a)*(k+b)*z / (4*(k+1)*kch*(k+c1h)) + d1 = kakbz*(e-(k+cba)*d*g) + e1 = kakbz*(d*abg+(k+c)*e) + ft = d*(k*(cba*z+k*z2-c)-abz)/(2*kch*nz) + f1 = f + e - ft + maxmag = max(maxmag, mag(f1)) + if mag(f1-f) < tol: + break + d, e, f = d1, e1, f1 + k += 1 + cancellation = maxmag - mag(f1) + if cancellation < extra: + break + else: + extra += cancellation + if extra > maxprec: + raise ctx.NoConvergence + return f1 + +@defun +def _hyp2f1(ctx, a_s, b_s, z, **kwargs): + (a, atype), (b, btype) = a_s + (c, ctype), = b_s + if z == 1: + # TODO: the following logic can be simplified + convergent = ctx.re(c-a-b) > 0 + finite = (ctx.isint(a) and a <= 0) or (ctx.isint(b) and b <= 0) + zerodiv = ctx.isint(c) and c <= 0 and not \ + ((ctx.isint(a) and c <= a <= 0) or (ctx.isint(b) and c <= b <= 0)) + #print "bz", a, b, c, z, convergent, finite, zerodiv + # Gauss's theorem gives the value if convergent + if (convergent or finite) and not zerodiv: + return ctx.gammaprod([c, c-a-b], [c-a, c-b], _infsign=True) + # Otherwise, there is a pole and we take the + # sign to be that when approaching from below + # XXX: this evaluation is not necessarily correct in all cases + return ctx.hyp2f1(a,b,c,1-ctx.eps*2) * ctx.inf + + # Equal to 1 (first term), unless there is a subsequent + # division by zero + if not z: + # Division by zero but power of z is higher than + # first order so cancels + if c or a == 0 or b == 0: + return 1+z + # Indeterminate + return ctx.nan + + # Hit zero denominator unless numerator goes to 0 first + if ctx.isint(c) and c <= 0: + if (ctx.isint(a) and c <= a <= 0) or \ + (ctx.isint(b) and c <= b <= 0): + pass + else: + # Pole in series + return ctx.inf + + absz = abs(z) + + # Fast case: standard series converges rapidly, + # possibly in finitely many terms + if absz <= 0.8 or (ctx.isint(a) and a <= 0 and a >= -1000) or \ + (ctx.isint(b) and b <= 0 and b >= -1000): + return ctx.hypsum(2, 1, (atype, btype, ctype), [a, b, c], z, **kwargs) + + orig = ctx.prec + try: + ctx.prec += 10 + + # Use 1/z transformation + if absz >= 1.3: + def h(a,b): + t = ctx.mpq_1-c; ab = a-b; rz = 1/z + T1 = ([-z],[-a], [c,-ab],[b,c-a], [a,t+a],[ctx.mpq_1+ab], rz) + T2 = ([-z],[-b], [c,ab],[a,c-b], [b,t+b],[ctx.mpq_1-ab], rz) + return T1, T2 + v = ctx.hypercomb(h, [a,b], **kwargs) + + # Use 1-z transformation + elif abs(1-z) <= 0.75: + def h(a,b): + t = c-a-b; ca = c-a; cb = c-b; rz = 1-z + T1 = [], [], [c,t], [ca,cb], [a,b], [1-t], rz + T2 = [rz], [t], [c,a+b-c], [a,b], [ca,cb], [1+t], rz + return T1, T2 + v = ctx.hypercomb(h, [a,b], **kwargs) + + # Use z/(z-1) transformation + elif abs(z/(z-1)) <= 0.75: + v = ctx.hyp2f1(a, c-b, c, z/(z-1)) / (1-z)**a + + # Remaining part of unit circle + else: + v = _hyp2f1_gosper(ctx,a,b,c,z,**kwargs) + + finally: + ctx.prec = orig + return +v + +@defun +def _hypq1fq(ctx, p, q, a_s, b_s, z, **kwargs): + r""" + Evaluates 3F2, 4F3, 5F4, ... + """ + a_s, a_types = zip(*a_s) + b_s, b_types = zip(*b_s) + a_s = list(a_s) + b_s = list(b_s) + absz = abs(z) + ispoly = False + for a in a_s: + if ctx.isint(a) and a <= 0: + ispoly = True + break + # Direct summation + if absz < 1 or ispoly: + try: + return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs) + except ctx.NoConvergence: + if absz > 1.1 or ispoly: + raise + # Use expansion at |z-1| -> 0. + # Reference: Wolfgang Buhring, "Generalized Hypergeometric Functions at + # Unit Argument", Proc. Amer. Math. Soc., Vol. 114, No. 1 (Jan. 1992), + # pp.145-153 + # The current implementation has several problems: + # 1. We only implement it for 3F2. The expansion coefficients are + # given by extremely messy nested sums in the higher degree cases + # (see reference). Is efficient sequential generation of the coefficients + # possible in the > 3F2 case? + # 2. Although the series converges, it may do so slowly, so we need + # convergence acceleration. The acceleration implemented by + # nsum does not always help, so results returned are sometimes + # inaccurate! Can we do better? + # 3. We should check conditions for convergence, and possibly + # do a better job of cancelling out gamma poles if possible. + if z == 1: + # XXX: should also check for division by zero in the + # denominator of the series (cf. hyp2f1) + S = ctx.re(sum(b_s)-sum(a_s)) + if S <= 0: + #return ctx.hyper(a_s, b_s, 1-ctx.eps*2, **kwargs) * ctx.inf + return ctx.hyper(a_s, b_s, 0.9, **kwargs) * ctx.inf + if (p,q) == (3,2) and abs(z-1) < 0.05: # and kwargs.get('sum1') + #print "Using alternate summation (experimental)" + a1,a2,a3 = a_s + b1,b2 = b_s + u = b1+b2-a3 + initial = ctx.gammaprod([b2-a3,b1-a3,a1,a2],[b2-a3,b1-a3,1,u]) + def term(k, _cache={0:initial}): + u = b1+b2-a3+k + if k in _cache: + t = _cache[k] + else: + t = _cache[k-1] + t *= (b1+k-a3-1)*(b2+k-a3-1) + t /= k*(u-1) + _cache[k] = t + return t * ctx.hyp2f1(a1,a2,u,z) + try: + S = ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'), + strict=kwargs.get('strict', True)) + return S * ctx.gammaprod([b1,b2],[a1,a2,a3]) + except ctx.NoConvergence: + pass + # Try to use convergence acceleration on and close to the unit circle. + # Problem: the convergence acceleration degenerates as |z-1| -> 0, + # except for special cases. Everywhere else, the Shanks transformation + # is very efficient. + if absz < 1.1 and ctx._re(z) <= 1: + + def term(kk, _cache={0:ctx.one}): + k = int(kk) + if k != kk: + t = z ** ctx.mpf(kk) / ctx.fac(kk) + for a in a_s: t *= ctx.rf(a,kk) + for b in b_s: t /= ctx.rf(b,kk) + return t + if k in _cache: + return _cache[k] + t = term(k-1) + m = k-1 + for j in xrange(p): t *= (a_s[j]+m) + for j in xrange(q): t /= (b_s[j]+m) + t *= z + t /= k + _cache[k] = t + return t + + sum_method = kwargs.get('sum_method', 'r+s+e') + + try: + return ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'), + strict=kwargs.get('strict', True), + method=sum_method.replace('e','')) + except ctx.NoConvergence: + if 'e' not in sum_method: + raise + pass + + if kwargs.get('verbose'): + print("Attempting Euler-Maclaurin summation") + + + """ + Somewhat slower version (one diffs_exp for each factor). + However, this would be faster with fast direct derivatives + of the gamma function. + + def power_diffs(k0): + r = 0 + l = ctx.log(z) + while 1: + yield z**ctx.mpf(k0) * l**r + r += 1 + + def loggamma_diffs(x, reciprocal=False): + sign = (-1) ** reciprocal + yield sign * ctx.loggamma(x) + i = 0 + while 1: + yield sign * ctx.psi(i,x) + i += 1 + + def hyper_diffs(k0): + b2 = b_s + [1] + A = [ctx.diffs_exp(loggamma_diffs(a+k0)) for a in a_s] + B = [ctx.diffs_exp(loggamma_diffs(b+k0,True)) for b in b2] + Z = [power_diffs(k0)] + C = ctx.gammaprod([b for b in b2], [a for a in a_s]) + for d in ctx.diffs_prod(A + B + Z): + v = C * d + yield v + """ + + def log_diffs(k0): + b2 = b_s + [1] + yield sum(ctx.loggamma(a+k0) for a in a_s) - \ + sum(ctx.loggamma(b+k0) for b in b2) + k0*ctx.log(z) + i = 0 + while 1: + v = sum(ctx.psi(i,a+k0) for a in a_s) - \ + sum(ctx.psi(i,b+k0) for b in b2) + if i == 0: + v += ctx.log(z) + yield v + i += 1 + + def hyper_diffs(k0): + C = ctx.gammaprod([b for b in b_s], [a for a in a_s]) + for d in ctx.diffs_exp(log_diffs(k0)): + v = C * d + yield v + + tol = ctx.eps / 1024 + prec = ctx.prec + try: + trunc = 50 * ctx.dps + ctx.prec += 20 + for i in xrange(5): + head = ctx.fsum(term(k) for k in xrange(trunc)) + tail, err = ctx.sumem(term, [trunc, ctx.inf], tol=tol, + adiffs=hyper_diffs(trunc), + verbose=kwargs.get('verbose'), + error=True, + _fast_abort=True) + if err < tol: + v = head + tail + break + trunc *= 2 + # Need to increase precision because calculation of + # derivatives may be inaccurate + ctx.prec += ctx.prec//2 + if i == 4: + raise ctx.NoConvergence(\ + "Euler-Maclaurin summation did not converge") + finally: + ctx.prec = prec + return +v + + # Use 1/z transformation + # http://functions.wolfram.com/HypergeometricFunctions/ + # HypergeometricPFQ/06/01/05/02/0004/ + def h(*args): + a_s = list(args[:p]) + b_s = list(args[p:]) + Ts = [] + recz = ctx.one/z + negz = ctx.fneg(z, exact=True) + for k in range(q+1): + ak = a_s[k] + C = [negz] + Cp = [-ak] + Gn = b_s + [ak] + [a_s[j]-ak for j in range(q+1) if j != k] + Gd = a_s + [b_s[j]-ak for j in range(q)] + Fn = [ak] + [ak-b_s[j]+1 for j in range(q)] + Fd = [1-a_s[j]+ak for j in range(q+1) if j != k] + Ts.append((C, Cp, Gn, Gd, Fn, Fd, recz)) + return Ts + return ctx.hypercomb(h, a_s+b_s, **kwargs) + +@defun +def _hyp_borel(ctx, p, q, a_s, b_s, z, **kwargs): + if a_s: + a_s, a_types = zip(*a_s) + a_s = list(a_s) + else: + a_s, a_types = [], () + if b_s: + b_s, b_types = zip(*b_s) + b_s = list(b_s) + else: + b_s, b_types = [], () + kwargs['maxterms'] = kwargs.get('maxterms', ctx.prec) + try: + return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs) + except ctx.NoConvergence: + pass + prec = ctx.prec + try: + tol = kwargs.get('asymp_tol', ctx.eps/4) + ctx.prec += 10 + # hypsum is has a conservative tolerance. So we try again: + def term(k, cache={0:ctx.one}): + if k in cache: + return cache[k] + t = term(k-1) + for a in a_s: t *= (a+(k-1)) + for b in b_s: t /= (b+(k-1)) + t *= z + t /= k + cache[k] = t + return t + s = ctx.one + for k in xrange(1, ctx.prec): + t = term(k) + s += t + if abs(t) <= tol: + return s + finally: + ctx.prec = prec + if p <= q+3: + contour = kwargs.get('contour') + if not contour: + if ctx.arg(z) < 0.25: + u = z / max(1, abs(z)) + if ctx.arg(z) >= 0: + contour = [0, 2j, (2j+2)/u, 2/u, ctx.inf] + else: + contour = [0, -2j, (-2j+2)/u, 2/u, ctx.inf] + #contour = [0, 2j/z, 2/z, ctx.inf] + #contour = [0, 2j, 2/z, ctx.inf] + #contour = [0, 2j, ctx.inf] + else: + contour = [0, ctx.inf] + quad_kwargs = kwargs.get('quad_kwargs', {}) + def g(t): + return ctx.exp(-t)*ctx.hyper(a_s, b_s+[1], t*z) + I, err = ctx.quad(g, contour, error=True, **quad_kwargs) + if err <= abs(I)*ctx.eps*8: + return I + raise ctx.NoConvergence + + +@defun +def _hyp2f2(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), (a2, a2type) = a_s + (b1, b1type), (b2, b2type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + orig = ctx.prec + + # Asymptotic expansion is ~ exp(z) + asymp_extraprec = magz + + # Asymptotic series is in terms of 3F1 + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 3) + + # TODO: much of the following could be shared with 2F3 instead of + # copypasted + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric2F2/06/02/02/0002/ + def h(a1,a2,b1,b2): + X = a1+a2-b1-b2 + A2 = a1+a2 + B2 = b1+b2 + c = {} + c[0] = ctx.one + c[1] = (A2-1)*X+b1*b2-a1*a2 + s1 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = 1-B2+2*a1+a1**2+2*a2+a2**2-A2*B2+a1*a2+b1*b2+(2*B2-3*(A2+1))*k+2*k**2 + uu2 = (k-A2+b1-1)*(k-A2+b2-1)*(k-X-2) + c[k] = ctx.one/k * (uu1*c[k-1]-uu2*c[k-2]) + t1 = c[k] * z**(-k) + if abs(t1) < 0.1*ctx.eps: + #print "Convergence :)" + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + #print "No convergence :(" + raise ctx.NoConvergence + s1 += t1 + tprev = t1 + k += 1 + S = ctx.exp(z)*s1 + T1 = [z,S], [X,1], [b1,b2],[a1,a2],[],[],0 + T2 = [-z],[-a1],[b1,b2,a2-a1],[a2,b1-a1,b2-a1],[a1,a1-b1+1,a1-b2+1],[a1-a2+1],-1/z + T3 = [-z],[-a2],[b1,b2,a1-a2],[a1,b1-a2,b2-a2],[a2,a2-b1+1,a2-b2+1],[-a1+a2+1],-1/z + return T1, T2, T3 + v = ctx.hypercomb(h, [a1,a2,b1,b2], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,z]) == 5: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + return ctx.hypsum(2, 2, (a1type, a2type, b1type, b2type), [a1, a2, b1, b2], z, **kwargs) + + + +@defun +def _hyp1f2(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), = a_s + (b1, b1type), (b2, b2type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + orig = ctx.prec + + # Asymptotic expansion is ~ exp(sqrt(z)) + asymp_extraprec = z and magz//2 + + # Asymptotic series is in terms of 3F0 + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 19) and \ + (ctx.sqrt(absz) > 1.5*orig) # and \ + # ctx._hyp_check_convergence([a1, a1-b1+1, a1-b2+1], [], + # 1/absz, orig+40+asymp_extraprec) + + # TODO: much of the following could be shared with 2F3 instead of + # copypasted + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric1F2/06/02/03/ + def h(a1,b1,b2): + X = ctx.mpq_1_2*(a1-b1-b2+ctx.mpq_1_2) + c = {} + c[0] = ctx.one + c[1] = 2*(ctx.mpq_1_4*(3*a1+b1+b2-2)*(a1-b1-b2)+b1*b2-ctx.mpq_3_16) + c[2] = 2*(b1*b2+ctx.mpq_1_4*(a1-b1-b2)*(3*a1+b1+b2-2)-ctx.mpq_3_16)**2+\ + ctx.mpq_1_16*(-16*(2*a1-3)*b1*b2 + \ + 4*(a1-b1-b2)*(-8*a1**2+11*a1+b1+b2-2)-3) + s1 = 0 + s2 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = (3*k**2+(-6*a1+2*b1+2*b2-4)*k + 3*a1**2 - \ + (b1-b2)**2 - 2*a1*(b1+b2-2) + ctx.mpq_1_4) + uu2 = (k-a1+b1-b2-ctx.mpq_1_2)*(k-a1-b1+b2-ctx.mpq_1_2)*\ + (k-a1+b1+b2-ctx.mpq_5_2) + c[k] = ctx.one/(2*k)*(uu1*c[k-1]-uu2*c[k-2]) + w = c[k] * (-z)**(-0.5*k) + t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w + t2 = ctx.j**k * ctx.mpf(2)**(-k) * w + if abs(t1) < 0.1*ctx.eps: + #print "Convergence :)" + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + #print "No convergence :(" + raise ctx.NoConvergence + s1 += t1 + s2 += t2 + tprev = t1 + k += 1 + S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \ + ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2 + T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2], [a1],\ + [], [], 0 + T2 = [-z], [-a1], [b1,b2],[b1-a1,b2-a1], \ + [a1,a1-b1+1,a1-b2+1], [], 1/z + return T1, T2 + v = ctx.hypercomb(h, [a1,b1,b2], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,b1,b2,z]) == 4: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + #print "not using asymp" + return ctx.hypsum(1, 2, (a1type, b1type, b2type), [a1, b1, b2], z, **kwargs) + + + +@defun +def _hyp2f3(ctx, a_s, b_s, z, **kwargs): + (a1, a1type), (a2, a2type) = a_s + (b1, b1type), (b2, b2type), (b3, b3type) = b_s + + absz = abs(z) + magz = ctx.mag(z) + + # Asymptotic expansion is ~ exp(sqrt(z)) + asymp_extraprec = z and magz//2 + orig = ctx.prec + + # Asymptotic series is in terms of 4F1 + # The square root below empirically provides a plausible criterion + # for the leading series to converge + can_use_asymptotic = (not kwargs.get('force_series')) and \ + (ctx.mag(absz) > 19) and (ctx.sqrt(absz) > 1.5*orig) + + if can_use_asymptotic: + #print "using asymp" + try: + try: + ctx.prec += asymp_extraprec + # http://functions.wolfram.com/HypergeometricFunctions/ + # Hypergeometric2F3/06/02/03/01/0002/ + def h(a1,a2,b1,b2,b3): + X = ctx.mpq_1_2*(a1+a2-b1-b2-b3+ctx.mpq_1_2) + A2 = a1+a2 + B3 = b1+b2+b3 + A = a1*a2 + B = b1*b2+b3*b2+b1*b3 + R = b1*b2*b3 + c = {} + c[0] = ctx.one + c[1] = 2*(B - A + ctx.mpq_1_4*(3*A2+B3-2)*(A2-B3) - ctx.mpq_3_16) + c[2] = ctx.mpq_1_2*c[1]**2 + ctx.mpq_1_16*(-16*(2*A2-3)*(B-A) + 32*R +\ + 4*(-8*A2**2 + 11*A2 + 8*A + B3 - 2)*(A2-B3)-3) + s1 = 0 + s2 = 0 + k = 0 + tprev = 0 + while 1: + if k not in c: + uu1 = (k-2*X-3)*(k-2*X-2*b1-1)*(k-2*X-2*b2-1)*\ + (k-2*X-2*b3-1) + uu2 = (4*(k-1)**3 - 6*(4*X+B3)*(k-1)**2 + \ + 2*(24*X**2+12*B3*X+4*B+B3-1)*(k-1) - 32*X**3 - \ + 24*B3*X**2 - 4*B - 8*R - 4*(4*B+B3-1)*X + 2*B3-1) + uu3 = (5*(k-1)**2+2*(-10*X+A2-3*B3+3)*(k-1)+2*c[1]) + c[k] = ctx.one/(2*k)*(uu1*c[k-3]-uu2*c[k-2]+uu3*c[k-1]) + w = c[k] * ctx.power(-z, -0.5*k) + t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w + t2 = ctx.j**k * ctx.mpf(2)**(-k) * w + if abs(t1) < 0.1*ctx.eps: + break + # Quit if the series doesn't converge quickly enough + if k > 5 and abs(tprev) / abs(t1) < 1.5: + raise ctx.NoConvergence + s1 += t1 + s2 += t2 + tprev = t1 + k += 1 + S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \ + ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2 + T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2, b3], [a1, a2],\ + [], [], 0 + T2 = [-z], [-a1], [b1,b2,b3,a2-a1],[a2,b1-a1,b2-a1,b3-a1], \ + [a1,a1-b1+1,a1-b2+1,a1-b3+1], [a1-a2+1], 1/z + T3 = [-z], [-a2], [b1,b2,b3,a1-a2],[a1,b1-a2,b2-a2,b3-a2], \ + [a2,a2-b1+1,a2-b2+1,a2-b3+1],[-a1+a2+1], 1/z + return T1, T2, T3 + v = ctx.hypercomb(h, [a1,a2,b1,b2,b3], force_series=True, maxterms=4*ctx.prec) + if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,b3,z]) == 6: + v = ctx.re(v) + return v + except ctx.NoConvergence: + pass + finally: + ctx.prec = orig + + return ctx.hypsum(2, 3, (a1type, a2type, b1type, b2type, b3type), [a1, a2, b1, b2, b3], z, **kwargs) + +@defun +def _hyp2f0(ctx, a_s, b_s, z, **kwargs): + (a, atype), (b, btype) = a_s + # We want to try aggressively to use the asymptotic expansion, + # and fall back only when absolutely necessary + try: + kwargsb = kwargs.copy() + kwargsb['maxterms'] = kwargsb.get('maxterms', ctx.prec) + return ctx.hypsum(2, 0, (atype,btype), [a,b], z, **kwargsb) + except ctx.NoConvergence: + if kwargs.get('force_series'): + raise + pass + def h(a, b): + w = ctx.sinpi(b) + rz = -1/z + T1 = ([ctx.pi,w,rz],[1,-1,a],[],[a-b+1,b],[a],[b],rz) + T2 = ([-ctx.pi,w,rz],[1,-1,1+a-b],[],[a,2-b],[a-b+1],[2-b],rz) + return T1, T2 + return ctx.hypercomb(h, [a, 1+a-b], **kwargs) + +@defun +def meijerg(ctx, a_s, b_s, z, r=1, series=None, **kwargs): + an, ap = a_s + bm, bq = b_s + n = len(an) + p = n + len(ap) + m = len(bm) + q = m + len(bq) + a = an+ap + b = bm+bq + a = [ctx.convert(_) for _ in a] + b = [ctx.convert(_) for _ in b] + z = ctx.convert(z) + if series is None: + if p < q: series = 1 + if p > q: series = 2 + if p == q: + if m+n == p and abs(z) > 1: + series = 2 + else: + series = 1 + if kwargs.get('verbose'): + print("Meijer G m,n,p,q,series =", m,n,p,q,series) + if series == 1: + def h(*args): + a = args[:p] + b = args[p:] + terms = [] + for k in range(m): + bases = [z] + expts = [b[k]/r] + gn = [b[j]-b[k] for j in range(m) if j != k] + gn += [1-a[j]+b[k] for j in range(n)] + gd = [a[j]-b[k] for j in range(n,p)] + gd += [1-b[j]+b[k] for j in range(m,q)] + hn = [1-a[j]+b[k] for j in range(p)] + hd = [1-b[j]+b[k] for j in range(q) if j != k] + hz = (-ctx.one)**(p-m-n) * z**(ctx.one/r) + terms.append((bases, expts, gn, gd, hn, hd, hz)) + return terms + else: + def h(*args): + a = args[:p] + b = args[p:] + terms = [] + for k in range(n): + bases = [z] + if r == 1: + expts = [a[k]-1] + else: + expts = [(a[k]-1)/ctx.convert(r)] + gn = [a[k]-a[j] for j in range(n) if j != k] + gn += [1-a[k]+b[j] for j in range(m)] + gd = [a[k]-b[j] for j in range(m,q)] + gd += [1-a[k]+a[j] for j in range(n,p)] + hn = [1-a[k]+b[j] for j in range(q)] + hd = [1+a[j]-a[k] for j in range(p) if j != k] + hz = (-ctx.one)**(q-m-n) / z**(ctx.one/r) + terms.append((bases, expts, gn, gd, hn, hd, hz)) + return terms + return ctx.hypercomb(h, a+b, **kwargs) + +@defun_wrapped +def appellf1(ctx,a,b1,b2,c,x,y,**kwargs): + # Assume x smaller + # We will use x for the outer loop + if abs(x) > abs(y): + x, y = y, x + b1, b2 = b2, b1 + def ok(x): + return abs(x) < 0.99 + # Finite cases + if ctx.isnpint(a): + pass + elif ctx.isnpint(b1): + pass + elif ctx.isnpint(b2): + x, y, b1, b2 = y, x, b2, b1 + else: + #print x, y + # Note: ok if |y| > 1, because + # 2F1 implements analytic continuation + if not ok(x): + u1 = (x-y)/(x-1) + if not ok(u1): + raise ValueError("Analytic continuation not implemented") + #print "Using analytic continuation" + return (1-x)**(-b1)*(1-y)**(c-a-b2)*\ + ctx.appellf1(c-a,b1,c-b1-b2,c,u1,y,**kwargs) + return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]}, {'m+n':[c]}, x,y, **kwargs) + +@defun +def appellf2(ctx,a,b1,b2,c1,c2,x,y,**kwargs): + # TODO: continuation + return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]}, + {'m':[c1],'n':[c2]}, x,y, **kwargs) + +@defun +def appellf3(ctx,a1,a2,b1,b2,c,x,y,**kwargs): + outer_polynomial = ctx.isnpint(a1) or ctx.isnpint(b1) + inner_polynomial = ctx.isnpint(a2) or ctx.isnpint(b2) + if not outer_polynomial: + if inner_polynomial or abs(x) > abs(y): + x, y = y, x + a1,a2,b1,b2 = a2,a1,b2,b1 + return ctx.hyper2d({'m':[a1,b1],'n':[a2,b2]}, {'m+n':[c]},x,y,**kwargs) + +@defun +def appellf4(ctx,a,b,c1,c2,x,y,**kwargs): + # TODO: continuation + return ctx.hyper2d({'m+n':[a,b]}, {'m':[c1],'n':[c2]},x,y,**kwargs) + +@defun +def hyper2d(ctx, a, b, x, y, **kwargs): + r""" + Sums the generalized 2D hypergeometric series + + .. math :: + + \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{P((a),m,n)}{Q((b),m,n)} + \frac{x^m y^n} {m! n!} + + where `(a) = (a_1,\ldots,a_r)`, `(b) = (b_1,\ldots,b_s)` and where + `P` and `Q` are products of rising factorials such as `(a_j)_n` or + `(a_j)_{m+n}`. `P` and `Q` are specified in the form of dicts, with + the `m` and `n` dependence as keys and parameter lists as values. + The supported rising factorials are given in the following table + (note that only a few are supported in `Q`): + + +------------+-------------------+--------+ + | Key | Rising factorial | `Q` | + +============+===================+========+ + | ``'m'`` | `(a_j)_m` | Yes | + +------------+-------------------+--------+ + | ``'n'`` | `(a_j)_n` | Yes | + +------------+-------------------+--------+ + | ``'m+n'`` | `(a_j)_{m+n}` | Yes | + +------------+-------------------+--------+ + | ``'m-n'`` | `(a_j)_{m-n}` | No | + +------------+-------------------+--------+ + | ``'n-m'`` | `(a_j)_{n-m}` | No | + +------------+-------------------+--------+ + | ``'2m+n'`` | `(a_j)_{2m+n}` | No | + +------------+-------------------+--------+ + | ``'2m-n'`` | `(a_j)_{2m-n}` | No | + +------------+-------------------+--------+ + | ``'2n-m'`` | `(a_j)_{2n-m}` | No | + +------------+-------------------+--------+ + + For example, the Appell F1 and F4 functions + + .. math :: + + F_1 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b)_m (c)_n}{(d)_{m+n}} + \frac{x^m y^n}{m! n!} + + F_4 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty} + \frac{(a)_{m+n} (b)_{m+n}}{(c)_m (d)_{n}} + \frac{x^m y^n}{m! n!} + + can be represented respectively as + + ``hyper2d({'m+n':[a], 'm':[b], 'n':[c]}, {'m+n':[d]}, x, y)`` + + ``hyper2d({'m+n':[a,b]}, {'m':[c], 'n':[d]}, x, y)`` + + More generally, :func:`~mpmath.hyper2d` can evaluate any of the 34 distinct + convergent second-order (generalized Gaussian) hypergeometric + series enumerated by Horn, as well as the Kampe de Feriet + function. + + The series is computed by rewriting it so that the inner + series (i.e. the series containing `n` and `y`) has the form of an + ordinary generalized hypergeometric series and thereby can be + evaluated efficiently using :func:`~mpmath.hyper`. If possible, + manually swapping `x` and `y` and the corresponding parameters + can sometimes give better results. + + **Examples** + + Two separable cases: a product of two geometric series, and a + product of two Gaussian hypergeometric functions:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> x, y = mpf(0.25), mpf(0.5) + >>> hyper2d({'m':1,'n':1}, {}, x,y) + 2.666666666666666666666667 + >>> 1/(1-x)/(1-y) + 2.666666666666666666666667 + >>> hyper2d({'m':[1,2],'n':[3,4]}, {'m':[5],'n':[6]}, x,y) + 4.164358531238938319669856 + >>> hyp2f1(1,2,5,x)*hyp2f1(3,4,6,y) + 4.164358531238938319669856 + + Some more series that can be done in closed form:: + + >>> hyper2d({'m':1,'n':1},{'m+n':1},x,y) + 2.013417124712514809623881 + >>> (exp(x)*x-exp(y)*y)/(x-y) + 2.013417124712514809623881 + + Six of the 34 Horn functions, G1-G3 and H1-H3:: + + >>> from mpmath import * + >>> mp.dps = 10; mp.pretty = True + >>> x, y = 0.0625, 0.125 + >>> a1,a2,b1,b2,c1,c2,d = 1.1,-1.2,-1.3,-1.4,1.5,-1.6,1.7 + >>> hyper2d({'m+n':a1,'n-m':b1,'m-n':b2},{},x,y) # G1 + 1.139090746 + >>> nsum(lambda m,n: rf(a1,m+n)*rf(b1,n-m)*rf(b2,m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 1.139090746 + >>> hyper2d({'m':a1,'n':a2,'n-m':b1,'m-n':b2},{},x,y) # G2 + 0.9503682696 + >>> nsum(lambda m,n: rf(a1,m)*rf(a2,n)*rf(b1,n-m)*rf(b2,m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 0.9503682696 + >>> hyper2d({'2n-m':a1,'2m-n':a2},{},x,y) # G3 + 1.029372029 + >>> nsum(lambda m,n: rf(a1,2*n-m)*rf(a2,2*m-n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 1.029372029 + >>> hyper2d({'m-n':a1,'m+n':b1,'n':c1},{'m':d},x,y) # H1 + -1.605331256 + >>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m+n)*rf(c1,n)/rf(d,m)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + -1.605331256 + >>> hyper2d({'m-n':a1,'m':b1,'n':[c1,c2]},{'m':d},x,y) # H2 + -2.35405404 + >>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m)*rf(c1,n)*rf(c2,n)/rf(d,m)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + -2.35405404 + >>> hyper2d({'2m+n':a1,'n':b1},{'m+n':c1},x,y) # H3 + 0.974479074 + >>> nsum(lambda m,n: rf(a1,2*m+n)*rf(b1,n)/rf(c1,m+n)*\ + ... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf]) + 0.974479074 + + **References** + + 1. [SrivastavaKarlsson]_ + 2. [Weisstein]_ http://mathworld.wolfram.com/HornFunction.html + 3. [Weisstein]_ http://mathworld.wolfram.com/AppellHypergeometricFunction.html + + """ + x = ctx.convert(x) + y = ctx.convert(y) + def parse(dct, key): + args = dct.pop(key, []) + try: + args = list(args) + except TypeError: + args = [args] + return [ctx.convert(arg) for arg in args] + a_s = dict(a) + b_s = dict(b) + a_m = parse(a, 'm') + a_n = parse(a, 'n') + a_m_add_n = parse(a, 'm+n') + a_m_sub_n = parse(a, 'm-n') + a_n_sub_m = parse(a, 'n-m') + a_2m_add_n = parse(a, '2m+n') + a_2m_sub_n = parse(a, '2m-n') + a_2n_sub_m = parse(a, '2n-m') + b_m = parse(b, 'm') + b_n = parse(b, 'n') + b_m_add_n = parse(b, 'm+n') + if a: raise ValueError("unsupported key: %r" % a.keys()[0]) + if b: raise ValueError("unsupported key: %r" % b.keys()[0]) + s = 0 + outer = ctx.one + m = ctx.mpf(0) + ok_count = 0 + prec = ctx.prec + maxterms = kwargs.get('maxterms', 20*prec) + try: + ctx.prec += 10 + tol = +ctx.eps + while 1: + inner_sign = 1 + outer_sign = 1 + inner_a = list(a_n) + inner_b = list(b_n) + outer_a = [a+m for a in a_m] + outer_b = [b+m for b in b_m] + # (a)_{m+n} = (a)_m (a+m)_n + for a in a_m_add_n: + a = a+m + inner_a.append(a) + outer_a.append(a) + # (b)_{m+n} = (b)_m (b+m)_n + for b in b_m_add_n: + b = b+m + inner_b.append(b) + outer_b.append(b) + # (a)_{n-m} = (a-m)_n / (a-m)_m + for a in a_n_sub_m: + inner_a.append(a-m) + outer_b.append(a-m-1) + # (a)_{m-n} = (-1)^(m+n) (1-a-m)_m / (1-a-m)_n + for a in a_m_sub_n: + inner_sign *= (-1) + outer_sign *= (-1)**(m) + inner_b.append(1-a-m) + outer_a.append(-a-m) + # (a)_{2m+n} = (a)_{2m} (a+2m)_n + for a in a_2m_add_n: + inner_a.append(a+2*m) + outer_a.append((a+2*m)*(1+a+2*m)) + # (a)_{2m-n} = (-1)^(2m+n) (1-a-2m)_{2m} / (1-a-2m)_n + for a in a_2m_sub_n: + inner_sign *= (-1) + inner_b.append(1-a-2*m) + outer_a.append((a+2*m)*(1+a+2*m)) + # (a)_{2n-m} = 4^n ((a-m)/2)_n ((a-m+1)/2)_n / (a-m)_m + for a in a_2n_sub_m: + inner_sign *= 4 + inner_a.append(0.5*(a-m)) + inner_a.append(0.5*(a-m+1)) + outer_b.append(a-m-1) + inner = ctx.hyper(inner_a, inner_b, inner_sign*y, + zeroprec=ctx.prec, **kwargs) + term = outer * inner * outer_sign + if abs(term) < tol: + ok_count += 1 + else: + ok_count = 0 + if ok_count >= 3 or not outer: + break + s += term + for a in outer_a: outer *= a + for b in outer_b: outer /= b + m += 1 + outer = outer * x / m + if m > maxterms: + raise ctx.NoConvergence("maxterms exceeded in hyper2d") + finally: + ctx.prec = prec + return +s + +""" +@defun +def kampe_de_feriet(ctx,a,b,c,d,e,f,x,y,**kwargs): + return ctx.hyper2d({'m+n':a,'m':b,'n':c}, + {'m+n':d,'m':e,'n':f}, x,y, **kwargs) +""" + +@defun +def bihyper(ctx, a_s, b_s, z, **kwargs): + r""" + Evaluates the bilateral hypergeometric series + + .. math :: + + \,_AH_B(a_1, \ldots, a_k; b_1, \ldots, b_B; z) = + \sum_{n=-\infty}^{\infty} + \frac{(a_1)_n \ldots (a_A)_n} + {(b_1)_n \ldots (b_B)_n} \, z^n + + where, for direct convergence, `A = B` and `|z| = 1`, although a + regularized sum exists more generally by considering the + bilateral series as a sum of two ordinary hypergeometric + functions. In order for the series to make sense, none of the + parameters may be integers. + + **Examples** + + The value of `\,_2H_2` at `z = 1` is given by Dougall's formula:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> a,b,c,d = 0.5, 1.5, 2.25, 3.25 + >>> bihyper([a,b],[c,d],1) + -14.49118026212345786148847 + >>> gammaprod([c,d,1-a,1-b,c+d-a-b-1],[c-a,d-a,c-b,d-b]) + -14.49118026212345786148847 + + The regularized function `\,_1H_0` can be expressed as the + sum of one `\,_2F_0` function and one `\,_1F_1` function:: + + >>> a = mpf(0.25) + >>> z = mpf(0.75) + >>> bihyper([a], [], z) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + >>> hyper([a,1],[],z) + (hyper([1],[1-a],-1/z)-1) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + >>> hyper([a,1],[],z) + hyper([1],[2-a],-1/z)/z/(a-1) + (0.2454393389657273841385582 + 0.2454393389657273841385582j) + + **References** + + 1. [Slater]_ (chapter 6: "Bilateral Series", pp. 180-189) + 2. [Wikipedia]_ http://en.wikipedia.org/wiki/Bilateral_hypergeometric_series + + """ + z = ctx.convert(z) + c_s = a_s + b_s + p = len(a_s) + q = len(b_s) + if (p, q) == (0,0) or (p, q) == (1,1): + return ctx.zero * z + neg = (p-q) % 2 + def h(*c_s): + a_s = list(c_s[:p]) + b_s = list(c_s[p:]) + aa_s = [2-b for b in b_s] + bb_s = [2-a for a in a_s] + rp = [(-1)**neg * z] + [1-b for b in b_s] + [1-a for a in a_s] + rc = [-1] + [1]*len(b_s) + [-1]*len(a_s) + T1 = [], [], [], [], a_s + [1], b_s, z + T2 = rp, rc, [], [], aa_s + [1], bb_s, (-1)**neg / z + return T1, T2 + return ctx.hypercomb(h, c_s, **kwargs) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/orthogonal.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/orthogonal.py new file mode 100644 index 0000000000000000000000000000000000000000..aa33d8bd78290f55a970e78dab7a317d5f652dee --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/orthogonal.py @@ -0,0 +1,493 @@ +from .functions import defun, defun_wrapped + +def _hermite_param(ctx, n, z, parabolic_cylinder): + """ + Combined calculation of the Hermite polynomial H_n(z) (and its + generalization to complex n) and the parabolic cylinder + function D. + """ + n, ntyp = ctx._convert_param(n) + z = ctx.convert(z) + q = -ctx.mpq_1_2 + # For re(z) > 0, 2F0 -- http://functions.wolfram.com/ + # HypergeometricFunctions/HermiteHGeneral/06/02/0009/ + # Otherwise, there is a reflection formula + # 2F0 + http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/16/01/01/0006/ + # + # TODO: + # An alternative would be to use + # http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/06/02/0006/ + # + # Also, the 1F1 expansion + # http://functions.wolfram.com/HypergeometricFunctions/ + # HermiteHGeneral/26/01/02/0001/ + # should probably be used for tiny z + if not z: + T1 = [2, ctx.pi], [n, 0.5], [], [q*(n-1)], [], [], 0 + if parabolic_cylinder: + T1[1][0] += q*n + return T1, + can_use_2f0 = ctx.isnpint(-n) or ctx.re(z) > 0 or \ + (ctx.re(z) == 0 and ctx.im(z) > 0) + expprec = ctx.prec*4 + 20 + if parabolic_cylinder: + u = ctx.fmul(ctx.fmul(z,z,prec=expprec), -0.25, exact=True) + w = ctx.fmul(z, ctx.sqrt(0.5,prec=expprec), prec=expprec) + else: + w = z + w2 = ctx.fmul(w, w, prec=expprec) + rw2 = ctx.fdiv(1, w2, prec=expprec) + nrw2 = ctx.fneg(rw2, exact=True) + nw = ctx.fneg(w, exact=True) + if can_use_2f0: + T1 = [2, w], [n, n], [], [], [q*n, q*(n-1)], [], nrw2 + terms = [T1] + else: + T1 = [2, nw], [n, n], [], [], [q*n, q*(n-1)], [], nrw2 + T2 = [2, ctx.pi, nw], [n+2, 0.5, 1], [], [q*n], [q*(n-1)], [1-q], w2 + terms = [T1,T2] + # Multiply by prefactor for D_n + if parabolic_cylinder: + expu = ctx.exp(u) + for i in range(len(terms)): + terms[i][1][0] += q*n + terms[i][0].append(expu) + terms[i][1].append(1) + return tuple(terms) + +@defun +def hermite(ctx, n, z, **kwargs): + return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 0), [], **kwargs) + +@defun +def pcfd(ctx, n, z, **kwargs): + r""" + Gives the parabolic cylinder function in Whittaker's notation + `D_n(z) = U(-n-1/2, z)` (see :func:`~mpmath.pcfu`). + It solves the differential equation + + .. math :: + + y'' + \left(n + \frac{1}{2} - \frac{1}{4} z^2\right) y = 0. + + and can be represented in terms of Hermite polynomials + (see :func:`~mpmath.hermite`) as + + .. math :: + + D_n(z) = 2^{-n/2} e^{-z^2/4} H_n\left(\frac{z}{\sqrt{2}}\right). + + **Plots** + + .. literalinclude :: /plots/pcfd.py + .. image :: /plots/pcfd.png + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> pcfd(0,0); pcfd(1,0); pcfd(2,0); pcfd(3,0) + 1.0 + 0.0 + -1.0 + 0.0 + >>> pcfd(4,0); pcfd(-3,0) + 3.0 + 0.6266570686577501256039413 + >>> pcfd('1/2', 2+3j) + (-5.363331161232920734849056 - 3.858877821790010714163487j) + >>> pcfd(2, -10) + 1.374906442631438038871515e-9 + + Verifying the differential equation:: + + >>> n = mpf(2.5) + >>> y = lambda z: pcfd(n,z) + >>> z = 1.75 + >>> chop(diff(y,z,2) + (n+0.5-0.25*z**2)*y(z)) + 0.0 + + Rational Taylor series expansion when `n` is an integer:: + + >>> taylor(lambda z: pcfd(5,z), 0, 7) + [0.0, 15.0, 0.0, -13.75, 0.0, 3.96875, 0.0, -0.6015625] + + """ + return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 1), [], **kwargs) + +@defun +def pcfu(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `U(a,z)`, which may be + defined for `\Re(z) > 0` in terms of the confluent + U-function (see :func:`~mpmath.hyperu`) by + + .. math :: + + U(a,z) = 2^{-\frac{1}{4}-\frac{a}{2}} e^{-\frac{1}{4} z^2} + U\left(\frac{a}{2}+\frac{1}{4}, + \frac{1}{2}, \frac{1}{2}z^2\right) + + or, for arbitrary `z`, + + .. math :: + + e^{-\frac{1}{4}z^2} U(a,z) = + U(a,0) \,_1F_1\left(-\tfrac{a}{2}+\tfrac{1}{4}; + \tfrac{1}{2}; -\tfrac{1}{2}z^2\right) + + U'(a,0) z \,_1F_1\left(-\tfrac{a}{2}+\tfrac{3}{4}; + \tfrac{3}{2}; -\tfrac{1}{2}z^2\right). + + **Examples** + + Connection to other functions:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> z = mpf(3) + >>> pcfu(0.5,z) + 0.03210358129311151450551963 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(z/sqrt(2)) + 0.03210358129311151450551963 + >>> pcfu(0.5,-z) + 23.75012332835297233711255 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2)) + 23.75012332835297233711255 + >>> pcfu(0.5,-z) + 23.75012332835297233711255 + >>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2)) + 23.75012332835297233711255 + + """ + n, _ = ctx._convert_param(a) + return ctx.pcfd(-n-ctx.mpq_1_2, z) + +@defun +def pcfv(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `V(a,z)`, which can be + represented in terms of :func:`~mpmath.pcfu` as + + .. math :: + + V(a,z) = \frac{\Gamma(a+\tfrac{1}{2}) (U(a,-z)-\sin(\pi a) U(a,z)}{\pi}. + + **Examples** + + Wronskian relation between `U` and `V`:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> a, z = 2, 3 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> sqrt(2/pi) + 0.7978845608028653558798921 + >>> a, z = 2.5, 3 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> a, z = 0.25, -1 + >>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z) + 0.7978845608028653558798921 + >>> a, z = 2+1j, 2+3j + >>> chop(pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z)) + 0.7978845608028653558798921 + + """ + n, ntype = ctx._convert_param(a) + z = ctx.convert(z) + q = ctx.mpq_1_2 + r = ctx.mpq_1_4 + if ntype == 'Q' and ctx.isint(n*2): + # Faster for half-integers + def h(): + jz = ctx.fmul(z, -1j, exact=True) + T1terms = _hermite_param(ctx, -n-q, z, 1) + T2terms = _hermite_param(ctx, n-q, jz, 1) + for T in T1terms: + T[0].append(1j) + T[1].append(1) + T[3].append(q-n) + u = ctx.expjpi((q*n-r)) * ctx.sqrt(2/ctx.pi) + for T in T2terms: + T[0].append(u) + T[1].append(1) + return T1terms + T2terms + v = ctx.hypercomb(h, [], **kwargs) + if ctx._is_real_type(n) and ctx._is_real_type(z): + v = ctx._re(v) + return v + else: + def h(n): + w = ctx.square_exp_arg(z, -0.25) + u = ctx.square_exp_arg(z, 0.5) + e = ctx.exp(w) + l = [ctx.pi, q, ctx.exp(w)] + Y1 = l, [-q, n*q+r, 1], [r-q*n], [], [q*n+r], [q], u + Y2 = l + [z], [-q, n*q-r, 1, 1], [1-r-q*n], [], [q*n+1-r], [1+q], u + c, s = ctx.cospi_sinpi(r+q*n) + Y1[0].append(s) + Y2[0].append(c) + for Y in (Y1, Y2): + Y[1].append(1) + Y[3].append(q-n) + return Y1, Y2 + return ctx.hypercomb(h, [n], **kwargs) + + +@defun +def pcfw(ctx, a, z, **kwargs): + r""" + Gives the parabolic cylinder function `W(a,z)` defined in (DLMF 12.14). + + **Examples** + + Value at the origin:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> a = mpf(0.25) + >>> pcfw(a,0) + 0.9722833245718180765617104 + >>> power(2,-0.75)*sqrt(abs(gamma(0.25+0.5j*a)/gamma(0.75+0.5j*a))) + 0.9722833245718180765617104 + >>> diff(pcfw,(a,0),(0,1)) + -0.5142533944210078966003624 + >>> -power(2,-0.25)*sqrt(abs(gamma(0.75+0.5j*a)/gamma(0.25+0.5j*a))) + -0.5142533944210078966003624 + + """ + n, _ = ctx._convert_param(a) + z = ctx.convert(z) + def terms(): + phi2 = ctx.arg(ctx.gamma(0.5 + ctx.j*n)) + phi2 = (ctx.loggamma(0.5+ctx.j*n) - ctx.loggamma(0.5-ctx.j*n))/2j + rho = ctx.pi/8 + 0.5*phi2 + # XXX: cancellation computing k + k = ctx.sqrt(1 + ctx.exp(2*ctx.pi*n)) - ctx.exp(ctx.pi*n) + C = ctx.sqrt(k/2) * ctx.exp(0.25*ctx.pi*n) + yield C * ctx.expj(rho) * ctx.pcfu(ctx.j*n, z*ctx.expjpi(-0.25)) + yield C * ctx.expj(-rho) * ctx.pcfu(-ctx.j*n, z*ctx.expjpi(0.25)) + v = ctx.sum_accurately(terms) + if ctx._is_real_type(n) and ctx._is_real_type(z): + v = ctx._re(v) + return v + +""" +Even/odd PCFs. Useful? + +@defun +def pcfy1(ctx, a, z, **kwargs): + a, _ = ctx._convert_param(n) + z = ctx.convert(z) + def h(): + w = ctx.square_exp_arg(z) + w1 = ctx.fmul(w, -0.25, exact=True) + w2 = ctx.fmul(w, 0.5, exact=True) + e = ctx.exp(w1) + return [e], [1], [], [], [ctx.mpq_1_2*a+ctx.mpq_1_4], [ctx.mpq_1_2], w2 + return ctx.hypercomb(h, [], **kwargs) + +@defun +def pcfy2(ctx, a, z, **kwargs): + a, _ = ctx._convert_param(n) + z = ctx.convert(z) + def h(): + w = ctx.square_exp_arg(z) + w1 = ctx.fmul(w, -0.25, exact=True) + w2 = ctx.fmul(w, 0.5, exact=True) + e = ctx.exp(w1) + return [e, z], [1, 1], [], [], [ctx.mpq_1_2*a+ctx.mpq_3_4], \ + [ctx.mpq_3_2], w2 + return ctx.hypercomb(h, [], **kwargs) +""" + +@defun_wrapped +def gegenbauer(ctx, n, a, z, **kwargs): + # Special cases: a+0.5, a*2 poles + if ctx.isnpint(a): + return 0*(z+n) + if ctx.isnpint(a+0.5): + # TODO: something else is required here + # E.g.: gegenbauer(-2, -0.5, 3) == -12 + if ctx.isnpint(n+1): + raise NotImplementedError("Gegenbauer function with two limits") + def h(a): + a2 = 2*a + T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z) + return [T] + return ctx.hypercomb(h, [a], **kwargs) + def h(n): + a2 = 2*a + T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z) + return [T] + return ctx.hypercomb(h, [n], **kwargs) + +@defun_wrapped +def jacobi(ctx, n, a, b, x, **kwargs): + if not ctx.isnpint(a): + def h(n): + return (([], [], [a+n+1], [n+1, a+1], [-n, a+b+n+1], [a+1], (1-x)*0.5),) + return ctx.hypercomb(h, [n], **kwargs) + if not ctx.isint(b): + def h(n, a): + return (([], [], [-b], [n+1, -b-n], [-n, a+b+n+1], [b+1], (x+1)*0.5),) + return ctx.hypercomb(h, [n, a], **kwargs) + # XXX: determine appropriate limit + return ctx.binomial(n+a,n) * ctx.hyp2f1(-n,1+n+a+b,a+1,(1-x)/2, **kwargs) + +@defun_wrapped +def laguerre(ctx, n, a, z, **kwargs): + # XXX: limits, poles + #if ctx.isnpint(n): + # return 0*(a+z) + def h(a): + return (([], [], [a+n+1], [a+1, n+1], [-n], [a+1], z),) + return ctx.hypercomb(h, [a], **kwargs) + +@defun_wrapped +def legendre(ctx, n, x, **kwargs): + if ctx.isint(n): + n = int(n) + # Accuracy near zeros + if (n + (n < 0)) & 1: + if not x: + return x + mag = ctx.mag(x) + if mag < -2*ctx.prec-10: + return x + if mag < -5: + ctx.prec += -mag + return ctx.hyp2f1(-n,n+1,1,(1-x)/2, **kwargs) + +@defun +def legenp(ctx, n, m, z, type=2, **kwargs): + # Legendre function, 1st kind + n = ctx.convert(n) + m = ctx.convert(m) + # Faster + if not m: + return ctx.legendre(n, z, **kwargs) + # TODO: correct evaluation at singularities + if type == 2: + def h(n,m): + g = m*0.5 + T = [1+z, 1-z], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z) + return (T,) + return ctx.hypercomb(h, [n,m], **kwargs) + if type == 3: + def h(n,m): + g = m*0.5 + T = [z+1, z-1], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z) + return (T,) + return ctx.hypercomb(h, [n,m], **kwargs) + raise ValueError("requires type=2 or type=3") + +@defun +def legenq(ctx, n, m, z, type=2, **kwargs): + # Legendre function, 2nd kind + n = ctx.convert(n) + m = ctx.convert(m) + z = ctx.convert(z) + if z in (1, -1): + #if ctx.isint(m): + # return ctx.nan + #return ctx.inf # unsigned + return ctx.nan + if type == 2: + def h(n, m): + cos, sin = ctx.cospi_sinpi(m) + s = 2 * sin / ctx.pi + c = cos + a = 1+z + b = 1-z + u = m/2 + w = (1-z)/2 + T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \ + [-n, n+1], [1-m], w + T2 = [-s, a, b], [-1, -u, u], [n+m+1], [n-m+1, m+1], \ + [-n, n+1], [m+1], w + return T1, T2 + return ctx.hypercomb(h, [n, m], **kwargs) + if type == 3: + # The following is faster when there only is a single series + # Note: not valid for -1 < z < 0 (?) + if abs(z) > 1: + def h(n, m): + T1 = [ctx.expjpi(m), 2, ctx.pi, z, z-1, z+1], \ + [1, -n-1, 0.5, -n-m-1, 0.5*m, 0.5*m], \ + [n+m+1], [n+1.5], \ + [0.5*(2+n+m), 0.5*(1+n+m)], [n+1.5], z**(-2) + return [T1] + return ctx.hypercomb(h, [n, m], **kwargs) + else: + # not valid for 1 < z < inf ? + def h(n, m): + s = 2 * ctx.sinpi(m) / ctx.pi + c = ctx.expjpi(m) + a = 1+z + b = z-1 + u = m/2 + w = (1-z)/2 + T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \ + [-n, n+1], [1-m], w + T2 = [-s, c, a, b], [-1, 1, -u, u], [n+m+1], [n-m+1, m+1], \ + [-n, n+1], [m+1], w + return T1, T2 + return ctx.hypercomb(h, [n, m], **kwargs) + raise ValueError("requires type=2 or type=3") + +@defun_wrapped +def chebyt(ctx, n, x, **kwargs): + if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1: + return x * 0 + return ctx.hyp2f1(-n,n,(1,2),(1-x)/2, **kwargs) + +@defun_wrapped +def chebyu(ctx, n, x, **kwargs): + if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1: + return x * 0 + return (n+1) * ctx.hyp2f1(-n, n+2, (3,2), (1-x)/2, **kwargs) + +@defun +def spherharm(ctx, l, m, theta, phi, **kwargs): + l = ctx.convert(l) + m = ctx.convert(m) + theta = ctx.convert(theta) + phi = ctx.convert(phi) + l_isint = ctx.isint(l) + l_natural = l_isint and l >= 0 + m_isint = ctx.isint(m) + if l_isint and l < 0 and m_isint: + return ctx.spherharm(-(l+1), m, theta, phi, **kwargs) + if theta == 0 and m_isint and m < 0: + return ctx.zero * 1j + if l_natural and m_isint: + if abs(m) > l: + return ctx.zero * 1j + # http://functions.wolfram.com/Polynomials/ + # SphericalHarmonicY/26/01/02/0004/ + def h(l,m): + absm = abs(m) + C = [-1, ctx.expj(m*phi), + (2*l+1)*ctx.fac(l+absm)/ctx.pi/ctx.fac(l-absm), + ctx.sin(theta)**2, + ctx.fac(absm), 2] + P = [0.5*m*(ctx.sign(m)+1), 1, 0.5, 0.5*absm, -1, -absm-1] + return ((C, P, [], [], [absm-l, l+absm+1], [absm+1], + ctx.sin(0.5*theta)**2),) + else: + # http://functions.wolfram.com/HypergeometricFunctions/ + # SphericalHarmonicYGeneral/26/01/02/0001/ + def h(l,m): + if ctx.isnpint(l-m+1) or ctx.isnpint(l+m+1) or ctx.isnpint(1-m): + return (([0], [-1], [], [], [], [], 0),) + cos, sin = ctx.cos_sin(0.5*theta) + C = [0.5*ctx.expj(m*phi), (2*l+1)/ctx.pi, + ctx.gamma(l-m+1), ctx.gamma(l+m+1), + cos**2, sin**2] + P = [1, 0.5, 0.5, -0.5, 0.5*m, -0.5*m] + return ((C, P, [], [1-m], [-l,l+1], [1-m], sin**2),) + return ctx.hypercomb(h, [l,m], **kwargs) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/qfunctions.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/qfunctions.py new file mode 100644 index 0000000000000000000000000000000000000000..5a20e53a8b6fa0d8fbc9ad098614d2694998f49a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/qfunctions.py @@ -0,0 +1,280 @@ +from .functions import defun, defun_wrapped + +@defun +def qp(ctx, a, q=None, n=None, **kwargs): + r""" + Evaluates the q-Pochhammer symbol (or q-rising factorial) + + .. math :: + + (a; q)_n = \prod_{k=0}^{n-1} (1-a q^k) + + where `n = \infty` is permitted if `|q| < 1`. Called with two arguments, + ``qp(a,q)`` computes `(a;q)_{\infty}`; with a single argument, ``qp(q)`` + computes `(q;q)_{\infty}`. The special case + + .. math :: + + \phi(q) = (q; q)_{\infty} = \prod_{k=1}^{\infty} (1-q^k) = + \sum_{k=-\infty}^{\infty} (-1)^k q^{(3k^2-k)/2} + + is also known as the Euler function, or (up to a factor `q^{-1/24}`) + the Dedekind eta function. + + **Examples** + + If `n` is a positive integer, the function amounts to a finite product:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qp(2,3,5) + -725305.0 + >>> fprod(1-2*3**k for k in range(5)) + -725305.0 + >>> qp(2,3,0) + 1.0 + + Complex arguments are allowed:: + + >>> qp(2-1j, 0.75j) + (0.4628842231660149089976379 + 4.481821753552703090628793j) + + The regular Pochhammer symbol `(a)_n` is obtained in the + following limit as `q \to 1`:: + + >>> a, n = 4, 7 + >>> limit(lambda q: qp(q**a,q,n) / (1-q)**n, 1) + 604800.0 + >>> rf(a,n) + 604800.0 + + The Taylor series of the reciprocal Euler function gives + the partition function `P(n)`, i.e. the number of ways of writing + `n` as a sum of positive integers:: + + >>> taylor(lambda q: 1/qp(q), 0, 10) + [1.0, 1.0, 2.0, 3.0, 5.0, 7.0, 11.0, 15.0, 22.0, 30.0, 42.0] + + Special values include:: + + >>> qp(0) + 1.0 + >>> findroot(diffun(qp), -0.4) # location of maximum + -0.4112484791779547734440257 + >>> qp(_) + 1.228348867038575112586878 + + The q-Pochhammer symbol is related to the Jacobi theta functions. + For example, the following identity holds:: + + >>> q = mpf(0.5) # arbitrary + >>> qp(q) + 0.2887880950866024212788997 + >>> root(3,-2)*root(q,-24)*jtheta(2,pi/6,root(q,6)) + 0.2887880950866024212788997 + + """ + a = ctx.convert(a) + if n is None: + n = ctx.inf + else: + n = ctx.convert(n) + if n < 0: + raise ValueError("n cannot be negative") + if q is None: + q = a + else: + q = ctx.convert(q) + if n == 0: + return ctx.one + 0*(a+q) + infinite = (n == ctx.inf) + same = (a == q) + if infinite: + if abs(q) >= 1: + if same and (q == -1 or q == 1): + return ctx.zero * q + raise ValueError("q-function only defined for |q| < 1") + elif q == 0: + return ctx.one - a + maxterms = kwargs.get('maxterms', 50*ctx.prec) + if infinite and same: + # Euler's pentagonal theorem + def terms(): + t = 1 + yield t + k = 1 + x1 = q + x2 = q**2 + while 1: + yield (-1)**k * x1 + yield (-1)**k * x2 + x1 *= q**(3*k+1) + x2 *= q**(3*k+2) + k += 1 + if k > maxterms: + raise ctx.NoConvergence + return ctx.sum_accurately(terms) + # return ctx.nprod(lambda k: 1-a*q**k, [0,n-1]) + def factors(): + k = 0 + r = ctx.one + while 1: + yield 1 - a*r + r *= q + k += 1 + if k >= n: + return + if k > maxterms: + raise ctx.NoConvergence + return ctx.mul_accurately(factors) + +@defun_wrapped +def qgamma(ctx, z, q, **kwargs): + r""" + Evaluates the q-gamma function + + .. math :: + + \Gamma_q(z) = \frac{(q; q)_{\infty}}{(q^z; q)_{\infty}} (1-q)^{1-z}. + + + **Examples** + + Evaluation for real and complex arguments:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qgamma(4,0.75) + 4.046875 + >>> qgamma(6,6) + 121226245.0 + >>> qgamma(3+4j, 0.5j) + (0.1663082382255199834630088 + 0.01952474576025952984418217j) + + The q-gamma function satisfies a functional equation similar + to that of the ordinary gamma function:: + + >>> q = mpf(0.25) + >>> z = mpf(2.5) + >>> qgamma(z+1,q) + 1.428277424823760954685912 + >>> (1-q**z)/(1-q)*qgamma(z,q) + 1.428277424823760954685912 + + """ + if abs(q) > 1: + return ctx.qgamma(z,1/q)*q**((z-2)*(z-1)*0.5) + return ctx.qp(q, q, None, **kwargs) / \ + ctx.qp(q**z, q, None, **kwargs) * (1-q)**(1-z) + +@defun_wrapped +def qfac(ctx, z, q, **kwargs): + r""" + Evaluates the q-factorial, + + .. math :: + + [n]_q! = (1+q)(1+q+q^2)\cdots(1+q+\cdots+q^{n-1}) + + or more generally + + .. math :: + + [z]_q! = \frac{(q;q)_z}{(1-q)^z}. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qfac(0,0) + 1.0 + >>> qfac(4,3) + 2080.0 + >>> qfac(5,6) + 121226245.0 + >>> qfac(1+1j, 2+1j) + (0.4370556551322672478613695 + 0.2609739839216039203708921j) + + """ + if ctx.isint(z) and ctx._re(z) > 0: + n = int(ctx._re(z)) + return ctx.qp(q, q, n, **kwargs) / (1-q)**n + return ctx.qgamma(z+1, q, **kwargs) + +@defun +def qhyper(ctx, a_s, b_s, q, z, **kwargs): + r""" + Evaluates the basic hypergeometric series or hypergeometric q-series + + .. math :: + + \,_r\phi_s \left[\begin{matrix} + a_1 & a_2 & \ldots & a_r \\ + b_1 & b_2 & \ldots & b_s + \end{matrix} ; q,z \right] = + \sum_{n=0}^\infty + \frac{(a_1;q)_n, \ldots, (a_r;q)_n} + {(b_1;q)_n, \ldots, (b_s;q)_n} + \left((-1)^n q^{n\choose 2}\right)^{1+s-r} + \frac{z^n}{(q;q)_n} + + where `(a;q)_n` denotes the q-Pochhammer symbol (see :func:`~mpmath.qp`). + + **Examples** + + Evaluation works for real and complex arguments:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> qhyper([0.5], [2.25], 0.25, 4) + -0.1975849091263356009534385 + >>> qhyper([0.5], [2.25], 0.25-0.25j, 4) + (2.806330244925716649839237 + 3.568997623337943121769938j) + >>> qhyper([1+j], [2,3+0.5j], 0.25, 3+4j) + (9.112885171773400017270226 - 1.272756997166375050700388j) + + Comparing with a summation of the defining series, using + :func:`~mpmath.nsum`:: + + >>> b, q, z = 3, 0.25, 0.5 + >>> qhyper([], [b], q, z) + 0.6221136748254495583228324 + >>> nsum(lambda n: z**n / qp(q,q,n)/qp(b,q,n) * q**(n*(n-1)), [0,inf]) + 0.6221136748254495583228324 + + """ + #a_s = [ctx._convert_param(a)[0] for a in a_s] + #b_s = [ctx._convert_param(b)[0] for b in b_s] + #q = ctx._convert_param(q)[0] + a_s = [ctx.convert(a) for a in a_s] + b_s = [ctx.convert(b) for b in b_s] + q = ctx.convert(q) + z = ctx.convert(z) + r = len(a_s) + s = len(b_s) + d = 1+s-r + maxterms = kwargs.get('maxterms', 50*ctx.prec) + def terms(): + t = ctx.one + yield t + qk = 1 + k = 0 + x = 1 + while 1: + for a in a_s: + p = 1 - a*qk + t *= p + for b in b_s: + p = 1 - b*qk + if not p: + raise ValueError + t /= p + t *= z + x *= (-1)**d * qk ** d + qk *= q + t /= (1 - qk) + k += 1 + yield t * x + if k > maxterms: + raise ctx.NoConvergence + return ctx.sum_accurately(terms) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/rszeta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/rszeta.py new file mode 100644 index 0000000000000000000000000000000000000000..19e2c9a251b81bafe8cf77a2b0180636b1078ee4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/rszeta.py @@ -0,0 +1,1403 @@ +""" +--------------------------------------------------------------------- +.. sectionauthor:: Juan Arias de Reyna + +This module implements zeta-related functions using the Riemann-Siegel +expansion: zeta_offline(s,k=0) + +* coef(J, eps): Need in the computation of Rzeta(s,k) + +* Rzeta_simul(s, der=0) computes Rzeta^(k)(s) and Rzeta^(k)(1-s) simultaneously + for 0 <= k <= der. Used by zeta_offline and z_offline + +* Rzeta_set(s, derivatives) computes Rzeta^(k)(s) for given derivatives, used by + z_half(t,k) and zeta_half + +* z_offline(w,k): Z(w) and its derivatives of order k <= 4 +* z_half(t,k): Z(t) (Riemann Siegel function) and its derivatives of order k <= 4 +* zeta_offline(s): zeta(s) and its derivatives of order k<= 4 +* zeta_half(1/2+it,k): zeta(s) and its derivatives of order k<= 4 + +* rs_zeta(s,k=0) Computes zeta^(k)(s) Unifies zeta_half and zeta_offline +* rs_z(w,k=0) Computes Z^(k)(w) Unifies z_offline and z_half +---------------------------------------------------------------------- + +This program uses Riemann-Siegel expansion even to compute +zeta(s) on points s = sigma + i t with sigma arbitrary not +necessarily equal to 1/2. + +It is founded on a new deduction of the formula, with rigorous +and sharp bounds for the terms and rest of this expansion. + +More information on the papers: + + J. Arias de Reyna, High Precision Computation of Riemann's + Zeta Function by the Riemann-Siegel Formula I, II + + We refer to them as I, II. + + In them we shall find detailed explanation of all the + procedure. + +The program uses Riemann-Siegel expansion. +This is useful when t is big, ( say t > 10000 ). +The precision is limited, roughly it can compute zeta(sigma+it) +with an error less than exp(-c t) for some constant c depending +on sigma. The program gives an error when the Riemann-Siegel +formula can not compute to the wanted precision. + +""" + +import math + +class RSCache(object): + def __init__(ctx): + ctx._rs_cache = [0, 10, {}, {}] + +from .functions import defun + +#-------------------------------------------------------------------------------# +# # +# coef(ctx, J, eps, _cache=[0, 10, {} ] ) # +# # +#-------------------------------------------------------------------------------# + +# This function computes the coefficients c[n] defined on (I, equation (47)) +# but see also (II, section 3.14). +# +# Since these coefficients are very difficult to compute we save the values +# in a cache. So if we compute several values of the functions Rzeta(s) for +# near values of s, we do not recompute these coefficients. +# +# c[n] are the Taylor coefficients of the function: +# +# F(z):= (exp(pi*j*(z*z/2+3/8))-j* sqrt(2) cos(pi*z/2))/(2*cos(pi *z)) +# +# + +def _coef(ctx, J, eps): + r""" + Computes the coefficients `c_n` for `0\le n\le 2J` with error less than eps + + **Definition** + + The coefficients c_n are defined by + + .. math :: + + \begin{equation} + F(z)=\frac{e^{\pi i + \bigl(\frac{z^2}{2}+\frac38\bigr)}-i\sqrt{2}\cos\frac{\pi}{2}z}{2\cos\pi + z}=\sum_{n=0}^\infty c_{2n} z^{2n} + \end{equation} + + they are computed applying the relation + + .. math :: + + \begin{multline} + c_{2n}=-\frac{i}{\sqrt{2}}\Bigl(\frac{\pi}{2}\Bigr)^{2n} + \sum_{k=0}^n\frac{(-1)^k}{(2k)!} + 2^{2n-2k}\frac{(-1)^{n-k}E_{2n-2k}}{(2n-2k)!}+\\ + +e^{3\pi i/8}\sum_{j=0}^n(-1)^j\frac{ + E_{2j}}{(2j)!}\frac{i^{n-j}\pi^{n+j}}{(n-j)!2^{n-j+1}}. + \end{multline} + """ + + newJ = J+2 # compute more coefficients that are needed + neweps6 = eps/2. # compute with a slight more precision that are needed + + # PREPARATION FOR THE COMPUTATION OF V(N) AND W(N) + # See II Section 3.16 + # + # Computing the exponent wpvw of the error II equation (81) + wpvw = max(ctx.mag(10*(newJ+3)), 4*newJ+5-ctx.mag(neweps6)) + + # Preparation of Euler numbers (we need until the 2*RS_NEWJ) + E = ctx._eulernum(2*newJ) + + # Now we have in the cache all the needed Euler numbers. + # + # Computing the powers of pi + # + # We need to compute the powers pi**n for 1<= n <= 2*J + # with relative error less than 2**(-wpvw) + # it is easy to show that this is obtained + # taking wppi as the least d with + # 2**d>40*J and 2**d> 4.24 *newJ + 2**wpvw + # In II Section 3.9 we need also that + # wppi > wptcoef[0], and that the powers + # here computed 0<= k <= 2*newJ are more + # than those needed there that are 2*L-2. + # so we need J >= L this will be checked + # before computing tcoef[] + wppi = max(ctx.mag(40*newJ), ctx.mag(newJ)+3 +wpvw) + ctx.prec = wppi + pipower = {} + pipower[0] = ctx.one + pipower[1] = ctx.pi + for n in range(2,2*newJ+1): + pipower[n] = pipower[n-1]*ctx.pi + + # COMPUTING THE COEFFICIENTS v(n) AND w(n) + # see II equation (61) and equations (81) and (82) + ctx.prec = wpvw+2 + v={} + w={} + for n in range(0,newJ+1): + va = (-1)**n * ctx._eulernum(2*n) + va = ctx.mpf(va)/ctx.fac(2*n) + v[n]=va*pipower[2*n] + for n in range(0,2*newJ+1): + wa = ctx.one/ctx.fac(n) + wa=wa/(2**n) + w[n]=wa*pipower[n] + + # COMPUTATION OF THE CONVOLUTIONS RS_P1 AND RS_P2 + # See II Section 3.16 + ctx.prec = 15 + wpp1a = 9 - ctx.mag(neweps6) + P1 = {} + for n in range(0,newJ+1): + ctx.prec = 15 + wpp1 = max(ctx.mag(10*(n+4)),4*n+wpp1a) + ctx.prec = wpp1 + sump = 0 + for k in range(0,n+1): + sump += ((-1)**k) * v[k]*w[2*n-2*k] + P1[n]=((-1)**(n+1))*ctx.j*sump + P2={} + for n in range(0,newJ+1): + ctx.prec = 15 + wpp2 = max(ctx.mag(10*(n+4)),4*n+wpp1a) + ctx.prec = wpp2 + sump = 0 + for k in range(0,n+1): + sump += (ctx.j**(n-k)) * v[k]*w[n-k] + P2[n]=sump + # COMPUTING THE COEFFICIENTS c[2n] + # See II Section 3.14 + ctx.prec = 15 + wpc0 = 5 - ctx.mag(neweps6) + wpc = max(6,4*newJ+wpc0) + ctx.prec = wpc + mu = ctx.sqrt(ctx.mpf('2'))/2 + nu = ctx.expjpi(3./8)/2 + c={} + for n in range(0,newJ): + ctx.prec = 15 + wpc = max(6,4*n+wpc0) + ctx.prec = wpc + c[2*n] = mu*P1[n]+nu*P2[n] + for n in range(1,2*newJ,2): + c[n] = 0 + return [newJ, neweps6, c, pipower] + +def coef(ctx, J, eps): + _cache = ctx._rs_cache + if J <= _cache[0] and eps >= _cache[1]: + return _cache[2], _cache[3] + orig = ctx._mp.prec + try: + data = _coef(ctx._mp, J, eps) + finally: + ctx._mp.prec = orig + if ctx is not ctx._mp: + data[2] = dict((k,ctx.convert(v)) for (k,v) in data[2].items()) + data[3] = dict((k,ctx.convert(v)) for (k,v) in data[3].items()) + ctx._rs_cache[:] = data + return ctx._rs_cache[2], ctx._rs_cache[3] + +#-------------------------------------------------------------------------------# +# # +# Rzeta_simul(s,k=0) # +# # +#-------------------------------------------------------------------------------# +# This function return a list with the values: +# Rzeta(sigma+it), conj(Rzeta(1-sigma+it)),Rzeta'(sigma+it), conj(Rzeta'(1-sigma+it)), +# .... , Rzeta^{(k)}(sigma+it), conj(Rzeta^{(k)}(1-sigma+it)) +# +# Useful to compute the function zeta(s) and Z(w) or its derivatives. +# + +def aux_M_Fp(ctx, xA, xeps4, a, xB1, xL): + # COMPUTING M NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + # See II Section 3.11 equations (47) and (48) + aux1 = 126.0657606*xA/xeps4 # 126.06.. = 316/sqrt(2*pi) + aux1 = ctx.ln(aux1) + aux2 = (2*ctx.ln(ctx.pi)+ctx.ln(xB1)+ctx.ln(a))/3 -ctx.ln(2*ctx.pi)/2 + m = 3*xL-3 + aux3= (ctx.loggamma(m+1)-ctx.loggamma(m/3.0+2))/2 -ctx.loggamma((m+1)/2.) + while((aux1 < m*aux2+ aux3)and (m>1)): + m = m - 1 + aux3 = (ctx.loggamma(m+1)-ctx.loggamma(m/3.0+2))/2 -ctx.loggamma((m+1)/2.) + xM = m + return xM + +def aux_J_needed(ctx, xA, xeps4, a, xB1, xM): + # DETERMINATION OF J THE NUMBER OF TERMS NEEDED + # IN THE TAYLOR SERIES OF F. + # See II Section 3.11 equation (49)) + # Only determine one + h1 = xeps4/(632*xA) + h2 = xB1*a * 126.31337419529260248 # = pi^2*e^2*sqrt(3) + h2 = h1 * ctx.power((h2/xM**2),(xM-1)/3) / xM + h3 = min(h1,h2) + return h3 + +def Rzeta_simul(ctx, s, der=0): + # First we take the value of ctx.prec + wpinitial = ctx.prec + + # INITIALIZATION + # Take the real and imaginary part of s + t = ctx._im(s) + xsigma = ctx._re(s) + ysigma = 1 - xsigma + + # Now compute several parameter that appear on the program + ctx.prec = 15 + a = ctx.sqrt(t/(2*ctx.pi)) + xasigma = a ** xsigma + yasigma = a ** ysigma + + # We need a simple bound A1 < asigma (see II Section 3.1 and 3.3) + xA1=ctx.power(2, ctx.mag(xasigma)-1) + yA1=ctx.power(2, ctx.mag(yasigma)-1) + + # We compute various epsilon's (see II end of Section 3.1) + eps = ctx.power(2, -wpinitial) + eps1 = eps/6. + xeps2 = eps * xA1/3. + yeps2 = eps * yA1/3. + + # COMPUTING SOME COEFFICIENTS THAT DEPENDS + # ON sigma + # constant b and c (see I Theorem 2 formula (26) ) + # coefficients A and B1 (see I Section 6.1 equation (50)) + # + # here we not need high precision + ctx.prec = 15 + if xsigma > 0: + xb = 2. + xc = math.pow(9,xsigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + xA = math.pow(9,xsigma) + xB1 = 1 + else: + xb = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + xc = math.pow(2,-xsigma)/4.44288 + xA = math.pow(2,-xsigma) + xB1 = 1.10789 # = 2*sqrt(1-log(2)) + + if(ysigma > 0): + yb = 2. + yc = math.pow(9,ysigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + yA = math.pow(9,ysigma) + yB1 = 1 + else: + yb = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + yc = math.pow(2,-ysigma)/4.44288 + yA = math.pow(2,-ysigma) + yB1 = 1.10789 # = 2*sqrt(1-log(2)) + + # COMPUTING L THE NUMBER OF TERMS NEEDED IN THE RIEMANN-SIEGEL + # CORRECTION + # See II Section 3.2 + ctx.prec = 15 + xL = 1 + while 3*xc*ctx.gamma(xL*0.5) * ctx.power(xb*a,-xL) >= xeps2: + xL = xL+1 + xL = max(2,xL) + yL = 1 + while 3*yc*ctx.gamma(yL*0.5) * ctx.power(yb*a,-yL) >= yeps2: + yL = yL+1 + yL = max(2,yL) + + # The number L has to satify some conditions. + # If not RS can not compute Rzeta(s) with the prescribed precision + # (see II, Section 3.2 condition (20) ) and + # (II, Section 3.3 condition (22) ). Also we have added + # an additional technical condition in Section 3.17 Proposition 17 + if ((3*xL >= 2*a*a/25.) or (3*xL+2+xsigma<0) or (abs(xsigma) > a/2.) or \ + (3*yL >= 2*a*a/25.) or (3*yL+2+ysigma<0) or (abs(ysigma) > a/2.)): + ctx.prec = wpinitial + raise NotImplementedError("Riemann-Siegel can not compute with such precision") + + # We take the maximum of the two values + L = max(xL, yL) + + # INITIALIZATION (CONTINUATION) + # + # eps3 is the constant defined on (II, Section 3.5 equation (27) ) + # each term of the RS correction must be computed with error <= eps3 + xeps3 = xeps2/(4*xL) + yeps3 = yeps2/(4*yL) + + # eps4 is defined on (II Section 3.6 equation (30) ) + # each component of the formula (II Section 3.6 equation (29) ) + # must be computed with error <= eps4 + xeps4 = xeps3/(3*xL) + yeps4 = yeps3/(3*yL) + + # COMPUTING M NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + xM = aux_M_Fp(ctx, xA, xeps4, a, xB1, xL) + yM = aux_M_Fp(ctx, yA, yeps4, a, yB1, yL) + M = max(xM, yM) + + # COMPUTING NUMBER OF TERMS J NEEDED + h3 = aux_J_needed(ctx, xA, xeps4, a, xB1, xM) + h4 = aux_J_needed(ctx, yA, yeps4, a, yB1, yM) + h3 = min(h3,h4) + J = 12 + jvalue = (2*ctx.pi)**J / ctx.gamma(J+1) + while jvalue > h3: + J = J+1 + jvalue = (2*ctx.pi)*jvalue/J + + # COMPUTING eps5[m] for 1 <= m <= 21 + # See II Section 10 equation (43) + # We choose the minimum of the two possibilities + eps5={} + xforeps5 = math.pi*math.pi*xB1*a + yforeps5 = math.pi*math.pi*yB1*a + for m in range(0,22): + xaux1 = math.pow(xforeps5, m/3)/(316.*xA) + yaux1 = math.pow(yforeps5, m/3)/(316.*yA) + aux1 = min(xaux1, yaux1) + aux2 = ctx.gamma(m+1)/ctx.gamma(m/3.0+0.5) + aux2 = math.sqrt(aux2) + eps5[m] = (aux1*aux2*min(xeps4,yeps4)) + + # COMPUTING wpfp + # See II Section 3.13 equation (59) + twenty = min(3*L-3, 21)+1 + aux = 6812*J + wpfp = ctx.mag(44*J) + for m in range(0,twenty): + wpfp = max(wpfp, ctx.mag(aux*ctx.gamma(m+1)/eps5[m])) + + # COMPUTING N AND p + # See II Section + ctx.prec = wpfp + ctx.mag(t)+20 + a = ctx.sqrt(t/(2*ctx.pi)) + N = ctx.floor(a) + p = 1-2*(a-N) + + # now we get a rounded version of p + # to the precision wpfp + # this possibly is not necessary + num=ctx.floor(p*(ctx.mpf('2')**wpfp)) + difference = p * (ctx.mpf('2')**wpfp)-num + if (difference < 0.5): + num = num + else: + num = num+1 + p = ctx.convert(num * (ctx.mpf('2')**(-wpfp))) + + # COMPUTING THE COEFFICIENTS c[n] = cc[n] + # We shall use the notation cc[n], since there is + # a constant that is called c + # See II Section 3.14 + # We compute the coefficients and also save then in a + # cache. The bulk of the computation is passed to + # the function coef() + # + # eps6 is defined in II Section 3.13 equation (58) + eps6 = ctx.power(ctx.convert(2*ctx.pi), J)/(ctx.gamma(J+1)*3*J) + + # Now we compute the coefficients + cc = {} + cont = {} + cont, pipowers = coef(ctx, J, eps6) + cc=cont.copy() # we need a copy since we have to change his values. + Fp={} # this is the adequate locus of this + for n in range(M, 3*L-2): + Fp[n] = 0 + Fp={} + ctx.prec = wpfp + for m in range(0,M+1): + sumP = 0 + for k in range(2*J-m-1,-1,-1): + sumP = (sumP * p)+ cc[k] + Fp[m] = sumP + # preparation of the new coefficients + for k in range(0,2*J-m-1): + cc[k] = (k+1)* cc[k+1] + + # COMPUTING THE NUMBERS xd[u,n,k], yd[u,n,k] + # See II Section 3.17 + # + # First we compute the working precisions xwpd[k] + # Se II equation (92) + xwpd={} + d1 = max(6,ctx.mag(40*L*L)) + xd2 = 13+ctx.mag((1+abs(xsigma))*xA)-ctx.mag(xeps4)-1 + xconst = ctx.ln(8/(ctx.pi*ctx.pi*a*a*xB1*xB1)) /2 + for n in range(0,L): + xd3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*xconst)+xd2 + xwpd[n]=max(xd3,d1) + + # procedure of II Section 3.17 + ctx.prec = xwpd[1]+10 + xpsigma = 1-(2*xsigma) + xd = {} + xd[0,0,-2]=0; xd[0,0,-1]=0; xd[0,0,0]=1; xd[0,0,1]=0 + xd[0,-1,-2]=0; xd[0,-1,-1]=0; xd[0,-1,0]=1; xd[0,-1,1]=0 + for n in range(1,L): + ctx.prec = xwpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if(m!=0): + m1 = ctx.one/m + c1= m1/4 + c2=(xpsigma*m1)/2 + c3=-(m+1) + xd[0,n,k]=c3*xd[0,n-1,k-2]+c1*xd[0,n-1,k]+c2*xd[0,n-1,k-1] + else: + xd[0,n,k]=0 + for r in range(0,k): + add=xd[0,n,r]*(ctx.mpf('1.0')*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + xd[0,n,k] -= ((-1)**(k-r))*add + xd[0,n,-2]=0; xd[0,n,-1]=0; xd[0,n,3*n//2+1]=0 + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if( (mu<0)or (n<0) or(k<0)or (k>3*n//2)): + xd[mu,n,k] = 0 + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = xwpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*xd[mu-2,n-2,k-3]+2*(xsigma+n-2)*xd[mu-1,n-2,k-3] + xd[mu,n,k] = aux - xd[mu-1,n-1,k-1] + + # Now we compute the working precisions ywpd[k] + # Se II equation (92) + ywpd={} + d1 = max(6,ctx.mag(40*L*L)) + yd2 = 13+ctx.mag((1+abs(ysigma))*yA)-ctx.mag(yeps4)-1 + yconst = ctx.ln(8/(ctx.pi*ctx.pi*a*a*yB1*yB1)) /2 + for n in range(0,L): + yd3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*yconst)+yd2 + ywpd[n]=max(yd3,d1) + + # procedure of II Section 3.17 + ctx.prec = ywpd[1]+10 + ypsigma = 1-(2*ysigma) + yd = {} + yd[0,0,-2]=0; yd[0,0,-1]=0; yd[0,0,0]=1; yd[0,0,1]=0 + yd[0,-1,-2]=0; yd[0,-1,-1]=0; yd[0,-1,0]=1; yd[0,-1,1]=0 + for n in range(1,L): + ctx.prec = ywpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if(m!=0): + m1 = ctx.one/m + c1= m1/4 + c2=(ypsigma*m1)/2 + c3=-(m+1) + yd[0,n,k]=c3*yd[0,n-1,k-2]+c1*yd[0,n-1,k]+c2*yd[0,n-1,k-1] + else: + yd[0,n,k]=0 + for r in range(0,k): + add=yd[0,n,r]*(ctx.mpf('1.0')*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + yd[0,n,k] -= ((-1)**(k-r))*add + yd[0,n,-2]=0; yd[0,n,-1]=0; yd[0,n,3*n//2+1]=0 + + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if( (mu<0)or (n<0) or(k<0)or (k>3*n//2)): + yd[mu,n,k] = 0 + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = ywpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*yd[mu-2,n-2,k-3]+2*(ysigma+n-2)*yd[mu-1,n-2,k-3] + yd[mu,n,k] = aux - yd[mu-1,n-1,k-1] + + # COMPUTING THE COEFFICIENTS xtcoef[k,l] + # See II Section 3.9 + # + # computing the needed wp + xwptcoef={} + xwpterm={} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + xc2 = ctx.mag(68*(L+2)*xA) + xc4 = ctx.mag(xB1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + xc3 = xc2 - k*xc4+ctx.mag(ctx.fac(k+0.5))/2. + xwptcoef[k] = (max(c1,xc3-ctx.mag(xeps4)+1)+1 +20)*1.5 + xwpterm[k] = (max(c1,ctx.mag(L+2)+xc3-ctx.mag(xeps3)+1)+1 +20) + ywptcoef={} + ywpterm={} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + yc2 = ctx.mag(68*(L+2)*yA) + yc4 = ctx.mag(yB1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + yc3 = yc2 - k*yc4+ctx.mag(ctx.fac(k+0.5))/2. + ywptcoef[k] = ((max(c1,yc3-ctx.mag(yeps4)+1))+10)*1.5 + ywpterm[k] = (max(c1,ctx.mag(L+2)+yc3-ctx.mag(yeps3)+1)+1)+10 + + # check of power of pi + # computing the fortcoef[mu,k,ell] + xfortcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + xfortcoef[mu,k,ell]=0 + for mu in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xfortcoef[mu,k,ell]=xd[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + xfortcoef[mu,k,ell]=xfortcoef[mu,k,ell]/((2*ctx.j)**ell) + + def trunc_a(t): + wp = ctx.prec + ctx.prec = wp + 2 + aa = ctx.sqrt(t/(2*ctx.pi)) + ctx.prec = wp + return aa + + # computing the tcoef[k,ell] + xtcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + xtcoef[mu,k,ell]=0 + ctx.prec = max(xwptcoef[0],ywptcoef[0])+3 + aa= trunc_a(t) + la = -ctx.ln(aa) + + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xtcoef[chi,k,ell] =0 + for mu in range(0, chi+1): + tcoefter=ctx.binomial(chi,mu)*ctx.power(la,mu)*xfortcoef[chi-mu,k,ell] + xtcoef[chi,k,ell] += tcoefter + + # COMPUTING THE COEFFICIENTS ytcoef[k,l] + # See II Section 3.9 + # + # computing the needed wp + # check of power of pi + # computing the fortcoef[mu,k,ell] + yfortcoef={} + for mu in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + yfortcoef[mu,k,ell]=0 + for mu in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + yfortcoef[mu,k,ell]=yd[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + yfortcoef[mu,k,ell]=yfortcoef[mu,k,ell]/((2*ctx.j)**ell) + # computing the tcoef[k,ell] + ytcoef={} + for chi in range(0,der+1): + for k in range(0,L): + for ell in range(-2,3*k//2+1): + ytcoef[chi,k,ell]=0 + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + ytcoef[chi,k,ell] =0 + for mu in range(0, chi+1): + tcoefter=ctx.binomial(chi,mu)*ctx.power(la,mu)*yfortcoef[chi-mu,k,ell] + ytcoef[chi,k,ell] += tcoefter + + # COMPUTING tv[k,ell] + # See II Section 3.8 + # + # a has a good value + ctx.prec = max(xwptcoef[0], ywptcoef[0])+2 + av = {} + av[0] = 1 + av[1] = av[0]/a + + ctx.prec = max(xwptcoef[0],ywptcoef[0]) + for k in range(2,L): + av[k] = av[k-1] * av[1] + + # Computing the quotients + xtv = {} + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = xwptcoef[k] + for ell in range(0,3*k//2+1): + xtv[chi,k,ell] = xtcoef[chi,k,ell]* av[k] + # Computing the quotients + ytv = {} + for chi in range(0,der+1): + for k in range(0,L): + ctx.prec = ywptcoef[k] + for ell in range(0,3*k//2+1): + ytv[chi,k,ell] = ytcoef[chi,k,ell]* av[k] + + # COMPUTING THE TERMS xterm[k] + # See II Section 3.6 + xterm = {} + for chi in range(0,der+1): + for n in range(0,L): + ctx.prec = xwpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += xtv[chi,n,k] + xterm[chi,n] = te + + # COMPUTING THE TERMS yterm[k] + # See II Section 3.6 + yterm = {} + for chi in range(0,der+1): + for n in range(0,L): + ctx.prec = ywpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += ytv[chi,n,k] + yterm[chi,n] = te + + # COMPUTING rssum + # See II Section 3.5 + xrssum={} + ctx.prec=15 + xrsbound = math.sqrt(ctx.pi) * xc /(xb*a) + ctx.prec=15 + xwprssum = ctx.mag(4.4*((L+3)**2)*xrsbound / xeps2) + xwprssum = max(xwprssum, ctx.mag(10*(L+1))) + ctx.prec = xwprssum + for chi in range(0,der+1): + xrssum[chi] = 0 + for k in range(1,L+1): + xrssum[chi] += xterm[chi,L-k] + yrssum={} + ctx.prec=15 + yrsbound = math.sqrt(ctx.pi) * yc /(yb*a) + ctx.prec=15 + ywprssum = ctx.mag(4.4*((L+3)**2)*yrsbound / yeps2) + ywprssum = max(ywprssum, ctx.mag(10*(L+1))) + ctx.prec = ywprssum + for chi in range(0,der+1): + yrssum[chi] = 0 + for k in range(1,L+1): + yrssum[chi] += yterm[chi,L-k] + + # COMPUTING S3 + # See II Section 3.19 + ctx.prec = 15 + A2 = 2**(max(ctx.mag(abs(xrssum[0])), ctx.mag(abs(yrssum[0])))) + eps8 = eps/(3*A2) + T = t *ctx.ln(t/(2*ctx.pi)) + xwps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-xsigma))*T) + ywps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-ysigma))*T) + + ctx.prec = max(xwps3, ywps3) + + tpi = t/(2*ctx.pi) + arg = (t/2)*ctx.ln(tpi)-(t/2)-ctx.pi/8 + U = ctx.expj(-arg) + a = trunc_a(t) + xasigma = ctx.power(a, -xsigma) + yasigma = ctx.power(a, -ysigma) + xS3 = ((-1)**(N-1)) * xasigma * U + yS3 = ((-1)**(N-1)) * yasigma * U + + # COMPUTING S1 the zetasum + # See II Section 3.18 + ctx.prec = 15 + xwpsum = 4+ ctx.mag((N+ctx.power(N,1-xsigma))*ctx.ln(N) /eps1) + ywpsum = 4+ ctx.mag((N+ctx.power(N,1-ysigma))*ctx.ln(N) /eps1) + wpsum = max(xwpsum, ywpsum) + + ctx.prec = wpsum +10 + ''' + # This can be improved + xS1={} + yS1={} + for chi in range(0,der+1): + xS1[chi] = 0 + yS1[chi] = 0 + for n in range(1,int(N)+1): + ln = ctx.ln(n) + xexpn = ctx.exp(-ln*(xsigma+ctx.j*t)) + yexpn = ctx.conj(1/(n*xexpn)) + for chi in range(0,der+1): + pown = ctx.power(-ln, chi) + xterm = pown*xexpn + yterm = pown*yexpn + xS1[chi] += xterm + yS1[chi] += yterm + ''' + xS1, yS1 = ctx._zetasum(s, 1, int(N)-1, range(0,der+1), True) + + # END OF COMPUTATION of xrz, yrz + # See II Section 3.1 + ctx.prec = 15 + xabsS1 = abs(xS1[der]) + xabsS2 = abs(xrssum[der] * xS3) + xwpend = max(6, wpinitial+ctx.mag(6*(3*xabsS1+7*xabsS2) ) ) + + ctx.prec = xwpend + xrz={} + for chi in range(0,der+1): + xrz[chi] = xS1[chi]+xrssum[chi]*xS3 + + ctx.prec = 15 + yabsS1 = abs(yS1[der]) + yabsS2 = abs(yrssum[der] * yS3) + ywpend = max(6, wpinitial+ctx.mag(6*(3*yabsS1+7*yabsS2) ) ) + + ctx.prec = ywpend + yrz={} + for chi in range(0,der+1): + yrz[chi] = yS1[chi]+yrssum[chi]*yS3 + yrz[chi] = ctx.conj(yrz[chi]) + ctx.prec = wpinitial + return xrz, yrz + +def Rzeta_set(ctx, s, derivatives=[0]): + r""" + Computes several derivatives of the auxiliary function of Riemann `R(s)`. + + **Definition** + + The function is defined by + + .. math :: + + \begin{equation} + {\mathop{\mathcal R }\nolimits}(s)= + \int_{0\swarrow1}\frac{x^{-s} e^{\pi i x^2}}{e^{\pi i x}- + e^{-\pi i x}}\,dx + \end{equation} + + To this function we apply the Riemann-Siegel expansion. + """ + der = max(derivatives) + # First we take the value of ctx.prec + # During the computation we will change ctx.prec, and finally we will + # restaurate the initial value + wpinitial = ctx.prec + # Take the real and imaginary part of s + t = ctx._im(s) + sigma = ctx._re(s) + # Now compute several parameter that appear on the program + ctx.prec = 15 + a = ctx.sqrt(t/(2*ctx.pi)) # Careful + asigma = ctx.power(a, sigma) # Careful + # We need a simple bound A1 < asigma (see II Section 3.1 and 3.3) + A1 = ctx.power(2, ctx.mag(asigma)-1) + # We compute various epsilon's (see II end of Section 3.1) + eps = ctx.power(2, -wpinitial) + eps1 = eps/6. + eps2 = eps * A1/3. + # COMPUTING SOME COEFFICIENTS THAT DEPENDS + # ON sigma + # constant b and c (see I Theorem 2 formula (26) ) + # coefficients A and B1 (see I Section 6.1 equation (50)) + # here we not need high precision + ctx.prec = 15 + if sigma > 0: + b = 2. + c = math.pow(9,sigma)/4.44288 + # 4.44288 =(math.sqrt(2)*math.pi) + A = math.pow(9,sigma) + B1 = 1 + else: + b = 2.25158 # math.sqrt( (3-2* math.log(2))*math.pi ) + c = math.pow(2,-sigma)/4.44288 + A = math.pow(2,-sigma) + B1 = 1.10789 # = 2*sqrt(1-log(2)) + # COMPUTING L THE NUMBER OF TERMS NEEDED IN THE RIEMANN-SIEGEL + # CORRECTION + # See II Section 3.2 + ctx.prec = 15 + L = 1 + while 3*c*ctx.gamma(L*0.5) * ctx.power(b*a,-L) >= eps2: + L = L+1 + L = max(2,L) + # The number L has to satify some conditions. + # If not RS can not compute Rzeta(s) with the prescribed precision + # (see II, Section 3.2 condition (20) ) and + # (II, Section 3.3 condition (22) ). Also we have added + # an additional technical condition in Section 3.17 Proposition 17 + if ((3*L >= 2*a*a/25.) or (3*L+2+sigma<0) or (abs(sigma)> a/2.)): + #print 'Error Riemann-Siegel can not compute with such precision' + ctx.prec = wpinitial + raise NotImplementedError("Riemann-Siegel can not compute with such precision") + + # INITIALIZATION (CONTINUATION) + # + # eps3 is the constant defined on (II, Section 3.5 equation (27) ) + # each term of the RS correction must be computed with error <= eps3 + eps3 = eps2/(4*L) + + # eps4 is defined on (II Section 3.6 equation (30) ) + # each component of the formula (II Section 3.6 equation (29) ) + # must be computed with error <= eps4 + eps4 = eps3/(3*L) + + # COMPUTING M. NUMBER OF DERIVATIVES Fp[m] TO COMPUTE + M = aux_M_Fp(ctx, A, eps4, a, B1, L) + Fp = {} + for n in range(M, 3*L-2): + Fp[n] = 0 + + # But I have not seen an instance of M != 3*L-3 + # + # DETERMINATION OF J THE NUMBER OF TERMS NEEDED + # IN THE TAYLOR SERIES OF F. + # See II Section 3.11 equation (49)) + h1 = eps4/(632*A) + h2 = ctx.pi*ctx.pi*B1*a *ctx.sqrt(3)*math.e*math.e + h2 = h1 * ctx.power((h2/M**2),(M-1)/3) / M + h3 = min(h1,h2) + J=12 + jvalue = (2*ctx.pi)**J / ctx.gamma(J+1) + while jvalue > h3: + J = J+1 + jvalue = (2*ctx.pi)*jvalue/J + + # COMPUTING eps5[m] for 1 <= m <= 21 + # See II Section 10 equation (43) + eps5={} + foreps5 = math.pi*math.pi*B1*a + for m in range(0,22): + aux1 = math.pow(foreps5, m/3)/(316.*A) + aux2 = ctx.gamma(m+1)/ctx.gamma(m/3.0+0.5) + aux2 = math.sqrt(aux2) + eps5[m] = aux1*aux2*eps4 + + # COMPUTING wpfp + # See II Section 3.13 equation (59) + twenty = min(3*L-3, 21)+1 + aux = 6812*J + wpfp = ctx.mag(44*J) + for m in range(0, twenty): + wpfp = max(wpfp, ctx.mag(aux*ctx.gamma(m+1)/eps5[m])) + # COMPUTING N AND p + # See II Section + ctx.prec = wpfp + ctx.mag(t) + 20 + a = ctx.sqrt(t/(2*ctx.pi)) + N = ctx.floor(a) + p = 1-2*(a-N) + + # now we get a rounded version of p to the precision wpfp + # this possibly is not necessary + num = ctx.floor(p*(ctx.mpf(2)**wpfp)) + difference = p * (ctx.mpf(2)**wpfp)-num + if difference < 0.5: + num = num + else: + num = num+1 + p = ctx.convert(num * (ctx.mpf(2)**(-wpfp))) + + # COMPUTING THE COEFFICIENTS c[n] = cc[n] + # We shall use the notation cc[n], since there is + # a constant that is called c + # See II Section 3.14 + # We compute the coefficients and also save then in a + # cache. The bulk of the computation is passed to + # the function coef() + # + # eps6 is defined in II Section 3.13 equation (58) + eps6 = ctx.power(2*ctx.pi, J)/(ctx.gamma(J+1)*3*J) + + # Now we compute the coefficients + cc={} + cont={} + cont, pipowers = coef(ctx, J, eps6) + cc = cont.copy() # we need a copy since we have + Fp={} + for n in range(M, 3*L-2): + Fp[n] = 0 + ctx.prec = wpfp + for m in range(0,M+1): + sumP = 0 + for k in range(2*J-m-1,-1,-1): + sumP = (sumP * p) + cc[k] + Fp[m] = sumP + # preparation of the new coefficients + for k in range(0, 2*J-m-1): + cc[k] = (k+1) * cc[k+1] + + # COMPUTING THE NUMBERS d[n,k] + # See II Section 3.17 + + # First we compute the working precisions wpd[k] + # Se II equation (92) + wpd = {} + d1 = max(6, ctx.mag(40*L*L)) + d2 = 13+ctx.mag((1+abs(sigma))*A)-ctx.mag(eps4)-1 + const = ctx.ln(8/(ctx.pi*ctx.pi*a*a*B1*B1)) /2 + for n in range(0,L): + d3 = ctx.mag(ctx.sqrt(ctx.gamma(n-0.5)))-ctx.floor(n*const)+d2 + wpd[n] = max(d3,d1) + + # procedure of II Section 3.17 + ctx.prec = wpd[1]+10 + psigma = 1-(2*sigma) + d = {} + d[0,0,-2]=0; d[0,0,-1]=0; d[0,0,0]=1; d[0,0,1]=0 + d[0,-1,-2]=0; d[0,-1,-1]=0; d[0,-1,0]=1; d[0,-1,1]=0 + for n in range(1,L): + ctx.prec = wpd[n]+10 + for k in range(0,3*n//2+1): + m = 3*n-2*k + if (m!=0): + m1 = ctx.one/m + c1 = m1/4 + c2 = (psigma*m1)/2 + c3 = -(m+1) + d[0,n,k] = c3*d[0,n-1,k-2]+c1*d[0,n-1,k]+c2*d[0,n-1,k-1] + else: + d[0,n,k]=0 + for r in range(0,k): + add = d[0,n,r]*(ctx.one*ctx.fac(2*k-2*r)/ctx.fac(k-r)) + d[0,n,k] -= ((-1)**(k-r))*add + d[0,n,-2]=0; d[0,n,-1]=0; d[0,n,3*n//2+1]=0 + + for mu in range(-2,der+1): + for n in range(-2,L): + for k in range(-3,max(1,3*n//2+2)): + if ((mu<0)or (n<0) or(k<0)or (k>3*n//2)): + d[mu,n,k] = 0 + + for mu in range(1,der+1): + for n in range(0,L): + ctx.prec = wpd[n]+10 + for k in range(0,3*n//2+1): + aux=(2*mu-2)*d[mu-2,n-2,k-3]+2*(sigma+n-2)*d[mu-1,n-2,k-3] + d[mu,n,k] = aux - d[mu-1,n-1,k-1] + + # COMPUTING THE COEFFICIENTS t[k,l] + # See II Section 3.9 + # + # computing the needed wp + wptcoef = {} + wpterm = {} + ctx.prec = 15 + c1 = ctx.mag(40*(L+2)) + c2 = ctx.mag(68*(L+2)*A) + c4 = ctx.mag(B1*a*math.sqrt(ctx.pi))-1 + for k in range(0,L): + c3 = c2 - k*c4+ctx.mag(ctx.fac(k+0.5))/2. + wptcoef[k] = max(c1,c3-ctx.mag(eps4)+1)+1 +10 + wpterm[k] = max(c1,ctx.mag(L+2)+c3-ctx.mag(eps3)+1)+1 +10 + + # check of power of pi + + # computing the fortcoef[mu,k,ell] + fortcoef={} + for mu in derivatives: + for k in range(0,L): + for ell in range(-2,3*k//2+1): + fortcoef[mu,k,ell]=0 + + for mu in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + fortcoef[mu,k,ell]=d[mu,k,ell]*Fp[3*k-2*ell]/pipowers[2*k-ell] + fortcoef[mu,k,ell]=fortcoef[mu,k,ell]/((2*ctx.j)**ell) + + def trunc_a(t): + wp = ctx.prec + ctx.prec = wp + 2 + aa = ctx.sqrt(t/(2*ctx.pi)) + ctx.prec = wp + return aa + + # computing the tcoef[chi,k,ell] + tcoef={} + for chi in derivatives: + for k in range(0,L): + for ell in range(-2,3*k//2+1): + tcoef[chi,k,ell]=0 + ctx.prec = wptcoef[0]+3 + aa = trunc_a(t) + la = -ctx.ln(aa) + + for chi in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + tcoef[chi,k,ell] = 0 + for mu in range(0, chi+1): + tcoefter = ctx.binomial(chi,mu) * la**mu * \ + fortcoef[chi-mu,k,ell] + tcoef[chi,k,ell] += tcoefter + + # COMPUTING tv[k,ell] + # See II Section 3.8 + + # Computing the powers av[k] = a**(-k) + ctx.prec = wptcoef[0] + 2 + + # a has a good value of a. + # See II Section 3.6 + av = {} + av[0] = 1 + av[1] = av[0]/a + + ctx.prec = wptcoef[0] + for k in range(2,L): + av[k] = av[k-1] * av[1] + + # Computing the quotients + tv = {} + for chi in derivatives: + for k in range(0,L): + ctx.prec = wptcoef[k] + for ell in range(0,3*k//2+1): + tv[chi,k,ell] = tcoef[chi,k,ell]* av[k] + + # COMPUTING THE TERMS term[k] + # See II Section 3.6 + term = {} + for chi in derivatives: + for n in range(0,L): + ctx.prec = wpterm[n] + te = 0 + for k in range(0, 3*n//2+1): + te += tv[chi,n,k] + term[chi,n] = te + + # COMPUTING rssum + # See II Section 3.5 + rssum={} + ctx.prec=15 + rsbound = math.sqrt(ctx.pi) * c /(b*a) + ctx.prec=15 + wprssum = ctx.mag(4.4*((L+3)**2)*rsbound / eps2) + wprssum = max(wprssum, ctx.mag(10*(L+1))) + ctx.prec = wprssum + for chi in derivatives: + rssum[chi] = 0 + for k in range(1,L+1): + rssum[chi] += term[chi,L-k] + + # COMPUTING S3 + # See II Section 3.19 + ctx.prec = 15 + A2 = 2**(ctx.mag(rssum[0])) + eps8 = eps/(3* A2) + T = t * ctx.ln(t/(2*ctx.pi)) + wps3 = 5 + ctx.mag((1+(2/eps8)*ctx.power(a,-sigma))*T) + + ctx.prec = wps3 + tpi = t/(2*ctx.pi) + arg = (t/2)*ctx.ln(tpi)-(t/2)-ctx.pi/8 + U = ctx.expj(-arg) + a = trunc_a(t) + asigma = ctx.power(a, -sigma) + S3 = ((-1)**(N-1)) * asigma * U + + # COMPUTING S1 the zetasum + # See II Section 3.18 + ctx.prec = 15 + wpsum = 4 + ctx.mag((N+ctx.power(N,1-sigma))*ctx.ln(N)/eps1) + + ctx.prec = wpsum + 10 + ''' + # This can be improved + S1 = {} + for chi in derivatives: + S1[chi] = 0 + for n in range(1,int(N)+1): + ln = ctx.ln(n) + expn = ctx.exp(-ln*(sigma+ctx.j*t)) + for chi in derivatives: + term = ctx.power(-ln, chi)*expn + S1[chi] += term + ''' + S1 = ctx._zetasum(s, 1, int(N)-1, derivatives)[0] + + # END OF COMPUTATION + # See II Section 3.1 + ctx.prec = 15 + absS1 = abs(S1[der]) + absS2 = abs(rssum[der] * S3) + wpend = max(6, wpinitial + ctx.mag(6*(3*absS1+7*absS2))) + ctx.prec = wpend + rz = {} + for chi in derivatives: + rz[chi] = S1[chi]+rssum[chi]*S3 + ctx.prec = wpinitial + return rz + + +def z_half(ctx,t,der=0): + r""" + z_half(t,der=0) Computes Z^(der)(t) + """ + s=ctx.mpf('0.5')+ctx.j*t + wpinitial = ctx.prec + ctx.prec = 15 + tt = t/(2*ctx.pi) + wptheta = wpinitial +1 + ctx.mag(3*(tt**1.5)*ctx.ln(tt)) + wpz = wpinitial + 1 + ctx.mag(12*tt*ctx.ln(tt)) + ctx.prec = wptheta + theta = ctx.siegeltheta(t) + ctx.prec = wpz + rz = Rzeta_set(ctx,s, range(der+1)) + if der > 0: ps1 = ctx._re(ctx.psi(0,s/2)/2 - ctx.ln(ctx.pi)/2) + if der > 1: ps2 = ctx._re(ctx.j*ctx.psi(1,s/2)/4) + if der > 2: ps3 = ctx._re(-ctx.psi(2,s/2)/8) + if der > 3: ps4 = ctx._re(-ctx.j*ctx.psi(3,s/2)/16) + exptheta = ctx.expj(theta) + if der == 0: + z = 2*exptheta*rz[0] + if der == 1: + zf = 2j*exptheta + z = zf*(ps1*rz[0]+rz[1]) + if der == 2: + zf = 2 * exptheta + z = -zf*(2*rz[1]*ps1+rz[0]*ps1**2+rz[2]-ctx.j*rz[0]*ps2) + if der == 3: + zf = -2j*exptheta + z = 3*rz[1]*ps1**2+rz[0]*ps1**3+3*ps1*rz[2] + z = zf*(z-3j*rz[1]*ps2-3j*rz[0]*ps1*ps2+rz[3]-rz[0]*ps3) + if der == 4: + zf = 2*exptheta + z = 4*rz[1]*ps1**3+rz[0]*ps1**4+6*ps1**2*rz[2] + z = z-12j*rz[1]*ps1*ps2-6j*rz[0]*ps1**2*ps2-6j*rz[2]*ps2-3*rz[0]*ps2*ps2 + z = z + 4*ps1*rz[3]-4*rz[1]*ps3-4*rz[0]*ps1*ps3+rz[4]+ctx.j*rz[0]*ps4 + z = zf*z + ctx.prec = wpinitial + return ctx._re(z) + +def zeta_half(ctx, s, k=0): + """ + zeta_half(s,k=0) Computes zeta^(k)(s) when Re s = 0.5 + """ + wpinitial = ctx.prec + sigma = ctx._re(s) + t = ctx._im(s) + #--- compute wptheta, wpR, wpbasic --- + ctx.prec = 53 + # X see II Section 3.21 (109) and (110) + if sigma > 0: + X = ctx.sqrt(abs(s)) + else: + X = (2*ctx.pi)**(sigma-1) * abs(1-s)**(0.5-sigma) + # M1 see II Section 3.21 (111) and (112) + if sigma > 0: + M1 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M1 = 4 * t * X + # T see II Section 3.21 (113) + abst = abs(0.5-s) + T = 2* abst*math.log(abst) + # computing wpbasic, wptheta, wpR see II Section 3.21 + wpbasic = max(6,3+ctx.mag(t)) + wpbasic2 = 2+ctx.mag(2.12*M1+21.2*M1*X+1.3*M1*X*T)+wpinitial+1 + wpbasic = max(wpbasic, wpbasic2) + wptheta = max(4, 3+ctx.mag(2.7*M1*X)+wpinitial+1) + wpR = 3+ctx.mag(1.1+2*X)+wpinitial+1 + ctx.prec = wptheta + theta = ctx.siegeltheta(t-ctx.j*(sigma-ctx.mpf('0.5'))) + if k > 0: ps1 = (ctx._re(ctx.psi(0,s/2)))/2 - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = -(ctx._im(ctx.psi(1,s/2)))/4 + if k > 2: ps3 = -(ctx._re(ctx.psi(2,s/2)))/8 + if k > 3: ps4 = (ctx._im(ctx.psi(3,s/2)))/16 + ctx.prec = wpR + xrz = Rzeta_set(ctx,s,range(k+1)) + yrz={} + for chi in range(0,k+1): + yrz[chi] = ctx.conj(xrz[chi]) + ctx.prec = wpbasic + exptheta = ctx.expj(-2*theta) + if k==0: + zv = xrz[0]+exptheta*yrz[0] + if k==1: + zv1 = -yrz[1] - 2*yrz[0]*ps1 + zv = xrz[1] + exptheta*zv1 + if k==2: + zv1 = 4*yrz[1]*ps1+4*yrz[0]*(ps1**2)+yrz[2]+2j*yrz[0]*ps2 + zv = xrz[2]+exptheta*zv1 + if k==3: + zv1 = -12*yrz[1]*ps1**2-8*yrz[0]*ps1**3-6*yrz[2]*ps1-6j*yrz[1]*ps2 + zv1 = zv1 - 12j*yrz[0]*ps1*ps2-yrz[3]+2*yrz[0]*ps3 + zv = xrz[3]+exptheta*zv1 + if k == 4: + zv1 = 32*yrz[1]*ps1**3 +16*yrz[0]*ps1**4+24*yrz[2]*ps1**2 + zv1 = zv1 +48j*yrz[1]*ps1*ps2+48j*yrz[0]*(ps1**2)*ps2 + zv1 = zv1+12j*yrz[2]*ps2-12*yrz[0]*ps2**2+8*yrz[3]*ps1-8*yrz[1]*ps3 + zv1 = zv1-16*yrz[0]*ps1*ps3+yrz[4]-2j*yrz[0]*ps4 + zv = xrz[4]+exptheta*zv1 + ctx.prec = wpinitial + return zv + +def zeta_offline(ctx, s, k=0): + """ + Computes zeta^(k)(s) off the line + """ + wpinitial = ctx.prec + sigma = ctx._re(s) + t = ctx._im(s) + #--- compute wptheta, wpR, wpbasic --- + ctx.prec = 53 + # X see II Section 3.21 (109) and (110) + if sigma > 0: + X = ctx.power(abs(s), 0.5) + else: + X = ctx.power(2*ctx.pi, sigma-1)*ctx.power(abs(1-s),0.5-sigma) + # M1 see II Section 3.21 (111) and (112) + if (sigma > 0): + M1 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M1 = 4 * t * X + # M2 see II Section 3.21 (111) and (112) + if (1-sigma > 0): + M2 = 2*ctx.sqrt(t/(2*ctx.pi)) + else: + M2 = 4*t*ctx.power(2*ctx.pi, -sigma)*ctx.power(abs(s),sigma-0.5) + # T see II Section 3.21 (113) + abst = abs(0.5-s) + T = 2* abst*math.log(abst) + # computing wpbasic, wptheta, wpR see II Section 3.21 + wpbasic = max(6,3+ctx.mag(t)) + wpbasic2 = 2+ctx.mag(2.12*M1+21.2*M2*X+1.3*M2*X*T)+wpinitial+1 + wpbasic = max(wpbasic, wpbasic2) + wptheta = max(4, 3+ctx.mag(2.7*M2*X)+wpinitial+1) + wpR = 3+ctx.mag(1.1+2*X)+wpinitial+1 + ctx.prec = wptheta + theta = ctx.siegeltheta(t-ctx.j*(sigma-ctx.mpf('0.5'))) + s1 = s + s2 = ctx.conj(1-s1) + ctx.prec = wpR + xrz, yrz = Rzeta_simul(ctx, s, k) + if k > 0: ps1 = (ctx.psi(0,s1/2)+ctx.psi(0,(1-s1)/2))/4 - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = ctx.j*(ctx.psi(1,s1/2)-ctx.psi(1,(1-s1)/2))/8 + if k > 2: ps3 = -(ctx.psi(2,s1/2)+ctx.psi(2,(1-s1)/2))/16 + if k > 3: ps4 = -ctx.j*(ctx.psi(3,s1/2)-ctx.psi(3,(1-s1)/2))/32 + ctx.prec = wpbasic + exptheta = ctx.expj(-2*theta) + if k == 0: + zv = xrz[0]+exptheta*yrz[0] + if k == 1: + zv1 = -yrz[1]-2*yrz[0]*ps1 + zv = xrz[1]+exptheta*zv1 + if k == 2: + zv1 = 4*yrz[1]*ps1+4*yrz[0]*(ps1**2) +yrz[2]+2j*yrz[0]*ps2 + zv = xrz[2]+exptheta*zv1 + if k == 3: + zv1 = -12*yrz[1]*ps1**2 -8*yrz[0]*ps1**3-6*yrz[2]*ps1-6j*yrz[1]*ps2 + zv1 = zv1 - 12j*yrz[0]*ps1*ps2-yrz[3]+2*yrz[0]*ps3 + zv = xrz[3]+exptheta*zv1 + if k == 4: + zv1 = 32*yrz[1]*ps1**3 +16*yrz[0]*ps1**4+24*yrz[2]*ps1**2 + zv1 = zv1 +48j*yrz[1]*ps1*ps2+48j*yrz[0]*(ps1**2)*ps2 + zv1 = zv1+12j*yrz[2]*ps2-12*yrz[0]*ps2**2+8*yrz[3]*ps1-8*yrz[1]*ps3 + zv1 = zv1-16*yrz[0]*ps1*ps3+yrz[4]-2j*yrz[0]*ps4 + zv = xrz[4]+exptheta*zv1 + ctx.prec = wpinitial + return zv + +def z_offline(ctx, w, k=0): + r""" + Computes Z(w) and its derivatives off the line + """ + s = ctx.mpf('0.5')+ctx.j*w + s1 = s + s2 = ctx.conj(1-s1) + wpinitial = ctx.prec + ctx.prec = 35 + # X see II Section 3.21 (109) and (110) + # M1 see II Section 3.21 (111) and (112) + if (ctx._re(s1) >= 0): + M1 = 2*ctx.sqrt(ctx._im(s1)/(2 * ctx.pi)) + X = ctx.sqrt(abs(s1)) + else: + X = (2*ctx.pi)**(ctx._re(s1)-1) * abs(1-s1)**(0.5-ctx._re(s1)) + M1 = 4 * ctx._im(s1)*X + # M2 see II Section 3.21 (111) and (112) + if (ctx._re(s2) >= 0): + M2 = 2*ctx.sqrt(ctx._im(s2)/(2 * ctx.pi)) + else: + M2 = 4 * ctx._im(s2)*(2*ctx.pi)**(ctx._re(s2)-1)*abs(1-s2)**(0.5-ctx._re(s2)) + # T see II Section 3.21 Prop. 27 + T = 2*abs(ctx.siegeltheta(w)) + # defining some precisions + # see II Section 3.22 (115), (116), (117) + aux1 = ctx.sqrt(X) + aux2 = aux1*(M1+M2) + aux3 = 3 +wpinitial + wpbasic = max(6, 3+ctx.mag(T), ctx.mag(aux2*(26+2*T))+aux3) + wptheta = max(4,ctx.mag(2.04*aux2)+aux3) + wpR = ctx.mag(4*aux1)+aux3 + # now the computations + ctx.prec = wptheta + theta = ctx.siegeltheta(w) + ctx.prec = wpR + xrz, yrz = Rzeta_simul(ctx,s,k) + pta = 0.25 + 0.5j*w + ptb = 0.25 - 0.5j*w + if k > 0: ps1 = 0.25*(ctx.psi(0,pta)+ctx.psi(0,ptb)) - ctx.ln(ctx.pi)/2 + if k > 1: ps2 = (1j/8)*(ctx.psi(1,pta)-ctx.psi(1,ptb)) + if k > 2: ps3 = (-1./16)*(ctx.psi(2,pta)+ctx.psi(2,ptb)) + if k > 3: ps4 = (-1j/32)*(ctx.psi(3,pta)-ctx.psi(3,ptb)) + ctx.prec = wpbasic + exptheta = ctx.expj(theta) + if k == 0: + zv = exptheta*xrz[0]+yrz[0]/exptheta + j = ctx.j + if k == 1: + zv = j*exptheta*(xrz[1]+xrz[0]*ps1)-j*(yrz[1]+yrz[0]*ps1)/exptheta + if k == 2: + zv = exptheta*(-2*xrz[1]*ps1-xrz[0]*ps1**2-xrz[2]+j*xrz[0]*ps2) + zv =zv + (-2*yrz[1]*ps1-yrz[0]*ps1**2-yrz[2]-j*yrz[0]*ps2)/exptheta + if k == 3: + zv1 = -3*xrz[1]*ps1**2-xrz[0]*ps1**3-3*xrz[2]*ps1+j*3*xrz[1]*ps2 + zv1 = (zv1+ 3j*xrz[0]*ps1*ps2-xrz[3]+xrz[0]*ps3)*j*exptheta + zv2 = 3*yrz[1]*ps1**2+yrz[0]*ps1**3+3*yrz[2]*ps1+j*3*yrz[1]*ps2 + zv2 = j*(zv2 + 3j*yrz[0]*ps1*ps2+ yrz[3]-yrz[0]*ps3)/exptheta + zv = zv1+zv2 + if k == 4: + zv1 = 4*xrz[1]*ps1**3+xrz[0]*ps1**4 + 6*xrz[2]*ps1**2 + zv1 = zv1-12j*xrz[1]*ps1*ps2-6j*xrz[0]*ps1**2*ps2-6j*xrz[2]*ps2 + zv1 = zv1-3*xrz[0]*ps2*ps2+4*xrz[3]*ps1-4*xrz[1]*ps3-4*xrz[0]*ps1*ps3 + zv1 = zv1+xrz[4]+j*xrz[0]*ps4 + zv2 = 4*yrz[1]*ps1**3+yrz[0]*ps1**4 + 6*yrz[2]*ps1**2 + zv2 = zv2+12j*yrz[1]*ps1*ps2+6j*yrz[0]*ps1**2*ps2+6j*yrz[2]*ps2 + zv2 = zv2-3*yrz[0]*ps2*ps2+4*yrz[3]*ps1-4*yrz[1]*ps3-4*yrz[0]*ps1*ps3 + zv2 = zv2+yrz[4]-j*yrz[0]*ps4 + zv = exptheta*zv1+zv2/exptheta + ctx.prec = wpinitial + return zv + +@defun +def rs_zeta(ctx, s, derivative=0, **kwargs): + if derivative > 4: + raise NotImplementedError + s = ctx.convert(s) + re = ctx._re(s); im = ctx._im(s) + if im < 0: + z = ctx.conj(ctx.rs_zeta(ctx.conj(s), derivative)) + return z + critical_line = (re == 0.5) + if critical_line: + return zeta_half(ctx, s, derivative) + else: + return zeta_offline(ctx, s, derivative) + +@defun +def rs_z(ctx, w, derivative=0): + w = ctx.convert(w) + re = ctx._re(w); im = ctx._im(w) + if re < 0: + return rs_z(ctx, -w, derivative) + critical_line = (im == 0) + if critical_line : + return z_half(ctx, w, derivative) + else: + return z_offline(ctx, w, derivative) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/signals.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/signals.py new file mode 100644 index 0000000000000000000000000000000000000000..6fadafb2dbb44fe19a2defa8d807d81d7c8e2789 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/signals.py @@ -0,0 +1,32 @@ +from .functions import defun_wrapped + +@defun_wrapped +def squarew(ctx, t, amplitude=1, period=1): + P = period + A = amplitude + return A*((-1)**ctx.floor(2*t/P)) + +@defun_wrapped +def trianglew(ctx, t, amplitude=1, period=1): + A = amplitude + P = period + + return 2*A*(0.5 - ctx.fabs(1 - 2*ctx.frac(t/P + 0.25))) + +@defun_wrapped +def sawtoothw(ctx, t, amplitude=1, period=1): + A = amplitude + P = period + return A*ctx.frac(t/P) + +@defun_wrapped +def unit_triangle(ctx, t, amplitude=1): + A = amplitude + if t <= -1 or t >= 1: + return ctx.zero + return A*(-ctx.fabs(t) + 1) + +@defun_wrapped +def sigmoid(ctx, t, amplitude=1): + A = amplitude + return A / (1 + ctx.exp(-t)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/theta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/theta.py new file mode 100644 index 0000000000000000000000000000000000000000..2b3d8323a163a43186b85417a1b40f3b656c30d0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/theta.py @@ -0,0 +1,1049 @@ +from .functions import defun, defun_wrapped + +@defun +def _jacobi_theta2(ctx, z, q): + extra1 = 10 + extra2 = 20 + # the loops below break when the fixed precision quantities + # a and b go to zero; + # right shifting small negative numbers by wp one obtains -1, not zero, + # so the condition a**2 + b**2 > MIN is used to break the loops. + MIN = 2 + if z == ctx.zero: + if (not ctx._im(q)): + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + s = x2 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + s += a + s = (1 << (wp+1)) + (s << 1) + s = ctx.ldexp(s, -wp) + else: + wp = ctx.prec + extra1 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp-1) + are = bre = x2re + aim = bim = x2im + sre = (1< MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + sre += are + sim += aim + sre = (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + else: + if (not ctx._im(q)) and (not ctx._im(z)): + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + s = c1 + ((a * cn) >> wp) + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + s += (a * cn) >> wp + s = (s << 1) + s = ctx.ldexp(s, -wp) + s *= ctx.nthroot(q, 4) + return s + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + sre = c1 + ((are * cn) >> wp) + sim = ((aim * cn) >> wp) + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + sre += ((are * cn) >> wp) + sim += ((aim * cn) >> wp) + sre = (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + #case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + prec0 = ctx.prec + ctx.prec = wp + c1, s1 = ctx.cos_sin(z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + #c2 = (c1*c1 - s1*s1) >> wp + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + #s2 = (c1 * s1) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + #cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + sre = c1re + ((a * cnre) >> wp) + sim = c1im + ((a * cnim) >> wp) + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + sre += ((a * cnre) >> wp) + sim += ((a * cnim) >> wp) + sre = (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + prec0 = ctx.prec + ctx.prec = wp + # cos(z), sin(z) with z complex + c1, s1 = ctx.cos_sin(z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + n = 1 + termre = c1re + termim = c1im + sre = c1re + ((are * cnre - aim * cnim) >> wp) + sim = c1im + ((are * cnim + aim * cnre) >> wp) + n = 3 + termre = ((are * cnre - aim * cnim) >> wp) + termim = ((are * cnim + aim * cnre) >> wp) + sre = c1re + ((are * cnre - aim * cnim) >> wp) + sim = c1im + ((are * cnim + aim * cnre) >> wp) + n = 5 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + #cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + termre = ((are * cnre - aim * cnim) >> wp) + termim = ((aim * cnre + are * cnim) >> wp) + sre += ((are * cnre - aim * cnim) >> wp) + sim += ((aim * cnre + are * cnim) >> wp) + n += 2 + sre = (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + s *= ctx.nthroot(q, 4) + return s + +@defun +def _djacobi_theta2(ctx, z, q, nd): + MIN = 2 + extra1 = 10 + extra2 = 20 + if (not ctx._im(q)) and (not ctx._im(z)): + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if (nd&1): + s = s1 + ((a * sn * 3**nd) >> wp) + else: + s = c1 + ((a * cn * 3**nd) >> wp) + n = 2 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if nd&1: + s += (a * sn * (2*n+1)**nd) >> wp + else: + s += (a * cn * (2*n+1)**nd) >> wp + n += 1 + s = -(s << 1) + s = ctx.ldexp(s, -wp) + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + c1, s1 = ctx.cos_sin(ctx._re(z), prec=wp) + cn = c1 = ctx.to_fixed(c1, wp) + sn = s1 = ctx.to_fixed(s1, wp) + c2 = (c1*c1 - s1*s1) >> wp + s2 = (c1 * s1) >> (wp - 1) + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + if (nd&1): + sre = s1 + ((are * sn * 3**nd) >> wp) + sim = ((aim * sn * 3**nd) >> wp) + else: + sre = c1 + ((are * cn * 3**nd) >> wp) + sim = ((aim * cn * 3**nd) >> wp) + n = 5 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + + if (nd&1): + sre += ((are * sn * n**nd) >> wp) + sim += ((aim * sn * n**nd) >> wp) + else: + sre += ((are * cn * n**nd) >> wp) + sim += ((aim * cn * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + #case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + x2 = (x*x) >> wp + a = b = x2 + prec0 = ctx.prec + ctx.prec = wp + c1, s1 = ctx.cos_sin(z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + #c2 = (c1*c1 - s1*s1) >> wp + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + #s2 = (c1 * s1) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + #cn, sn = (cn*c2 - sn*s2) >> wp, (sn*c2 + cn*s2) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if (nd&1): + sre = s1re + ((a * snre * 3**nd) >> wp) + sim = s1im + ((a * snim * 3**nd) >> wp) + else: + sre = c1re + ((a * cnre * 3**nd) >> wp) + sim = c1im + ((a * cnim * 3**nd) >> wp) + n = 5 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if (nd&1): + sre += ((a * snre * n**nd) >> wp) + sim += ((a * snim * n**nd) >> wp) + else: + sre += ((a * cnre * n**nd) >> wp) + sim += ((a * cnim * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = x2re + aim = bim = x2im + prec0 = ctx.prec + ctx.prec = wp + # cos(2*z), sin(2*z) with z complex + c1, s1 = ctx.cos_sin(z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + c2re = (c1re*c1re - c1im*c1im - s1re*s1re + s1im*s1im) >> wp + c2im = (c1re*c1im - s1re*s1im) >> (wp - 1) + s2re = (c1re*s1re - c1im*s1im) >> (wp - 1) + s2im = (c1re*s1im + c1im*s1re) >> (wp - 1) + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if (nd&1): + sre = s1re + (((are * snre - aim * snim) * 3**nd) >> wp) + sim = s1im + (((are * snim + aim * snre)* 3**nd) >> wp) + else: + sre = c1re + (((are * cnre - aim * cnim) * 3**nd) >> wp) + sim = c1im + (((are * cnim + aim * cnre)* 3**nd) >> wp) + n = 5 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + #cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + t1 = (cnre*c2re - cnim*c2im - snre*s2re + snim*s2im) >> wp + t2 = (cnre*c2im + cnim*c2re - snre*s2im - snim*s2re) >> wp + t3 = (snre*c2re - snim*c2im + cnre*s2re - cnim*s2im) >> wp + t4 = (snre*c2im + snim*c2re + cnre*s2im + cnim*s2re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if (nd&1): + sre += (((are * snre - aim * snim) * n**nd) >> wp) + sim += (((aim * snre + are * snim) * n**nd) >> wp) + else: + sre += (((are * cnre - aim * cnim) * n**nd) >> wp) + sim += (((aim * cnre + are * cnim) * n**nd) >> wp) + n += 2 + sre = -(sre << 1) + sim = -(sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + s *= ctx.nthroot(q, 4) + if (nd&1): + return (-1)**(nd//2) * s + else: + return (-1)**(1 + nd//2) * s + +@defun +def _jacobi_theta3(ctx, z, q): + extra1 = 10 + extra2 = 20 + MIN = 2 + if z == ctx.zero: + if not ctx._im(q): + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + s = x + a = b = x + x2 = (x*x) >> wp + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + s += a + s = (1 << wp) + (s << 1) + s = ctx.ldexp(s, -wp) + return s + else: + wp = ctx.prec + extra1 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + sre = are = bre = xre + sim = aim = bim = xim + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + sre += are + sim += aim + sre = (1 << wp) + (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + return s + else: + if (not ctx._im(q)) and (not ctx._im(z)): + s = 0 + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + a = b = x + x2 = (x*x) >> wp + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + s += (a * cn) >> wp + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + s += (a * cn) >> wp + s = (1 << wp) + (s << 1) + s = ctx.ldexp(s, -wp) + return s + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = xre + aim = bim = xim + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + sre = (are * cn) >> wp + sim = (aim * cn) >> wp + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + sre += (are * cn) >> wp + sim += (aim * cn) >> wp + sre = (1 << wp) + (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + return s + #case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + a = b = x + x2 = (x*x) >> wp + prec0 = ctx.prec + ctx.prec = wp + c1, s1 = ctx.cos_sin(2*z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + sre = (a * cnre) >> wp + sim = (a * cnim) >> wp + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + sre += (a * cnre) >> wp + sim += (a * cnim) >> wp + sre = (1 << wp) + (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + return s + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = xre + aim = bim = xim + prec0 = ctx.prec + ctx.prec = wp + # cos(2*z), sin(2*z) with z complex + c1, s1 = ctx.cos_sin(2*z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + sre = (are * cnre - aim * cnim) >> wp + sim = (aim * cnre + are * cnim) >> wp + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + sre += (are * cnre - aim * cnim) >> wp + sim += (aim * cnre + are * cnim) >> wp + sre = (1 << wp) + (sre << 1) + sim = (sim << 1) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + return s + +@defun +def _djacobi_theta3(ctx, z, q, nd): + """nd=1,2,3 order of the derivative with respect to z""" + MIN = 2 + extra1 = 10 + extra2 = 20 + if (not ctx._im(q)) and (not ctx._im(z)): + s = 0 + wp = ctx.prec + extra1 + x = ctx.to_fixed(ctx._re(q), wp) + a = b = x + x2 = (x*x) >> wp + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + if (nd&1): + s += (a * sn) >> wp + else: + s += (a * cn) >> wp + n = 2 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + if nd&1: + s += (a * sn * n**nd) >> wp + else: + s += (a * cn * n**nd) >> wp + n += 1 + s = -(s << (nd+1)) + s = ctx.ldexp(s, -wp) + # case z real, q complex + elif not ctx._im(z): + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = xre + aim = bim = xim + c1, s1 = ctx.cos_sin(ctx._re(z)*2, prec=wp) + c1 = ctx.to_fixed(c1, wp) + s1 = ctx.to_fixed(s1, wp) + cn = c1 + sn = s1 + if (nd&1): + sre = (are * sn) >> wp + sim = (aim * sn) >> wp + else: + sre = (are * cn) >> wp + sim = (aim * cn) >> wp + n = 2 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + cn, sn = (cn*c1 - sn*s1) >> wp, (sn*c1 + cn*s1) >> wp + if nd&1: + sre += (are * sn * n**nd) >> wp + sim += (aim * sn * n**nd) >> wp + else: + sre += (are * cn * n**nd) >> wp + sim += (aim * cn * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + #case z complex, q real + elif not ctx._im(q): + wp = ctx.prec + extra2 + x = ctx.to_fixed(ctx._re(q), wp) + a = b = x + x2 = (x*x) >> wp + prec0 = ctx.prec + ctx.prec = wp + c1, s1 = ctx.cos_sin(2*z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + if (nd&1): + sre = (a * snre) >> wp + sim = (a * snim) >> wp + else: + sre = (a * cnre) >> wp + sim = (a * cnim) >> wp + n = 2 + while abs(a) > MIN: + b = (b*x2) >> wp + a = (a*b) >> wp + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if (nd&1): + sre += (a * snre * n**nd) >> wp + sim += (a * snim * n**nd) >> wp + else: + sre += (a * cnre * n**nd) >> wp + sim += (a * cnim * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + # case z and q complex + else: + wp = ctx.prec + extra2 + xre = ctx.to_fixed(ctx._re(q), wp) + xim = ctx.to_fixed(ctx._im(q), wp) + x2re = (xre*xre - xim*xim) >> wp + x2im = (xre*xim) >> (wp - 1) + are = bre = xre + aim = bim = xim + prec0 = ctx.prec + ctx.prec = wp + # cos(2*z), sin(2*z) with z complex + c1, s1 = ctx.cos_sin(2*z) + ctx.prec = prec0 + cnre = c1re = ctx.to_fixed(ctx._re(c1), wp) + cnim = c1im = ctx.to_fixed(ctx._im(c1), wp) + snre = s1re = ctx.to_fixed(ctx._re(s1), wp) + snim = s1im = ctx.to_fixed(ctx._im(s1), wp) + if (nd&1): + sre = (are * snre - aim * snim) >> wp + sim = (aim * snre + are * snim) >> wp + else: + sre = (are * cnre - aim * cnim) >> wp + sim = (aim * cnre + are * cnim) >> wp + n = 2 + while are**2 + aim**2 > MIN: + bre, bim = (bre * x2re - bim * x2im) >> wp, \ + (bre * x2im + bim * x2re) >> wp + are, aim = (are * bre - aim * bim) >> wp, \ + (are * bim + aim * bre) >> wp + t1 = (cnre*c1re - cnim*c1im - snre*s1re + snim*s1im) >> wp + t2 = (cnre*c1im + cnim*c1re - snre*s1im - snim*s1re) >> wp + t3 = (snre*c1re - snim*c1im + cnre*s1re - cnim*s1im) >> wp + t4 = (snre*c1im + snim*c1re + cnre*s1im + cnim*s1re) >> wp + cnre = t1 + cnim = t2 + snre = t3 + snim = t4 + if(nd&1): + sre += ((are * snre - aim * snim) * n**nd) >> wp + sim += ((aim * snre + are * snim) * n**nd) >> wp + else: + sre += ((are * cnre - aim * cnim) * n**nd) >> wp + sim += ((aim * cnre + are * cnim) * n**nd) >> wp + n += 1 + sre = -(sre << (nd+1)) + sim = -(sim << (nd+1)) + sre = ctx.ldexp(sre, -wp) + sim = ctx.ldexp(sim, -wp) + s = ctx.mpc(sre, sim) + if (nd&1): + return (-1)**(nd//2) * s + else: + return (-1)**(1 + nd//2) * s + +@defun +def _jacobi_theta2a(ctx, z, q): + """ + case ctx._im(z) != 0 + theta(2, z, q) = + q**1/4 * Sum(q**(n*n + n) * exp(j*(2*n + 1)*z), n=-inf, inf) + max term for minimum (2*n+1)*log(q).real - 2* ctx._im(z) + n0 = int(ctx._im(z)/log(q).real - 1/2) + theta(2, z, q) = + q**1/4 * Sum(q**(n*n + n) * exp(j*(2*n + 1)*z), n=n0, inf) + + q**1/4 * Sum(q**(n*n + n) * exp(j*(2*n + 1)*z), n, n0-1, -inf) + """ + n = n0 = int(ctx._im(z)/ctx._re(ctx.log(q)) - 1/2) + e2 = ctx.expj(2*z) + e = e0 = ctx.expj((2*n+1)*z) + a = q**(n*n + n) + # leading term + term = a * e + s = term + eps1 = ctx.eps*abs(term) + while 1: + n += 1 + e = e * e2 + term = q**(n*n + n) * e + if abs(term) < eps1: + break + s += term + e = e0 + e2 = ctx.expj(-2*z) + n = n0 + while 1: + n -= 1 + e = e * e2 + term = q**(n*n + n) * e + if abs(term) < eps1: + break + s += term + s = s * ctx.nthroot(q, 4) + return s + +@defun +def _jacobi_theta3a(ctx, z, q): + """ + case ctx._im(z) != 0 + theta3(z, q) = Sum(q**(n*n) * exp(j*2*n*z), n, -inf, inf) + max term for n*abs(log(q).real) + ctx._im(z) ~= 0 + n0 = int(- ctx._im(z)/abs(log(q).real)) + """ + n = n0 = int(-ctx._im(z)/abs(ctx._re(ctx.log(q)))) + e2 = ctx.expj(2*z) + e = e0 = ctx.expj(2*n*z) + s = term = q**(n*n) * e + eps1 = ctx.eps*abs(term) + while 1: + n += 1 + e = e * e2 + term = q**(n*n) * e + if abs(term) < eps1: + break + s += term + e = e0 + e2 = ctx.expj(-2*z) + n = n0 + while 1: + n -= 1 + e = e * e2 + term = q**(n*n) * e + if abs(term) < eps1: + break + s += term + return s + +@defun +def _djacobi_theta2a(ctx, z, q, nd): + """ + case ctx._im(z) != 0 + dtheta(2, z, q, nd) = + j* q**1/4 * Sum(q**(n*n + n) * (2*n+1)*exp(j*(2*n + 1)*z), n=-inf, inf) + max term for (2*n0+1)*log(q).real - 2* ctx._im(z) ~= 0 + n0 = int(ctx._im(z)/log(q).real - 1/2) + """ + n = n0 = int(ctx._im(z)/ctx._re(ctx.log(q)) - 1/2) + e2 = ctx.expj(2*z) + e = e0 = ctx.expj((2*n + 1)*z) + a = q**(n*n + n) + # leading term + term = (2*n+1)**nd * a * e + s = term + eps1 = ctx.eps*abs(term) + while 1: + n += 1 + e = e * e2 + term = (2*n+1)**nd * q**(n*n + n) * e + if abs(term) < eps1: + break + s += term + e = e0 + e2 = ctx.expj(-2*z) + n = n0 + while 1: + n -= 1 + e = e * e2 + term = (2*n+1)**nd * q**(n*n + n) * e + if abs(term) < eps1: + break + s += term + return ctx.j**nd * s * ctx.nthroot(q, 4) + +@defun +def _djacobi_theta3a(ctx, z, q, nd): + """ + case ctx._im(z) != 0 + djtheta3(z, q, nd) = (2*j)**nd * + Sum(q**(n*n) * n**nd * exp(j*2*n*z), n, -inf, inf) + max term for minimum n*abs(log(q).real) + ctx._im(z) + """ + n = n0 = int(-ctx._im(z)/abs(ctx._re(ctx.log(q)))) + e2 = ctx.expj(2*z) + e = e0 = ctx.expj(2*n*z) + a = q**(n*n) * e + s = term = n**nd * a + if n != 0: + eps1 = ctx.eps*abs(term) + else: + eps1 = ctx.eps*abs(a) + while 1: + n += 1 + e = e * e2 + a = q**(n*n) * e + term = n**nd * a + if n != 0: + aterm = abs(term) + else: + aterm = abs(a) + if aterm < eps1: + break + s += term + e = e0 + e2 = ctx.expj(-2*z) + n = n0 + while 1: + n -= 1 + e = e * e2 + a = q**(n*n) * e + term = n**nd * a + if n != 0: + aterm = abs(term) + else: + aterm = abs(a) + if aterm < eps1: + break + s += term + return (2*ctx.j)**nd * s + +@defun +def jtheta(ctx, n, z, q, derivative=0): + if derivative: + return ctx._djtheta(n, z, q, derivative) + + z = ctx.convert(z) + q = ctx.convert(q) + + # Implementation note + # If ctx._im(z) is close to zero, _jacobi_theta2 and _jacobi_theta3 + # are used, + # which compute the series starting from n=0 using fixed precision + # numbers; + # otherwise _jacobi_theta2a and _jacobi_theta3a are used, which compute + # the series starting from n=n0, which is the largest term. + + # TODO: write _jacobi_theta2a and _jacobi_theta3a using fixed-point + + if abs(q) > ctx.THETA_Q_LIM: + raise ValueError('abs(q) > THETA_Q_LIM = %f' % ctx.THETA_Q_LIM) + + extra = 10 + if z: + M = ctx.mag(z) + if M > 5 or (n == 1 and M < -5): + extra += 2*abs(M) + cz = 0.5 + extra2 = 50 + prec0 = ctx.prec + try: + ctx.prec += extra + if n == 1: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._jacobi_theta2(z - ctx.pi/2, q) + else: + ctx.dps += 10 + res = ctx._jacobi_theta2a(z - ctx.pi/2, q) + else: + res = ctx._jacobi_theta2(z - ctx.pi/2, q) + elif n == 2: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._jacobi_theta2(z, q) + else: + ctx.dps += 10 + res = ctx._jacobi_theta2a(z, q) + else: + res = ctx._jacobi_theta2(z, q) + elif n == 3: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._jacobi_theta3(z, q) + else: + ctx.dps += 10 + res = ctx._jacobi_theta3a(z, q) + else: + res = ctx._jacobi_theta3(z, q) + elif n == 4: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._jacobi_theta3(z, -q) + else: + ctx.dps += 10 + res = ctx._jacobi_theta3a(z, -q) + else: + res = ctx._jacobi_theta3(z, -q) + else: + raise ValueError + finally: + ctx.prec = prec0 + return res + +@defun +def _djtheta(ctx, n, z, q, derivative=1): + z = ctx.convert(z) + q = ctx.convert(q) + nd = int(derivative) + + if abs(q) > ctx.THETA_Q_LIM: + raise ValueError('abs(q) > THETA_Q_LIM = %f' % ctx.THETA_Q_LIM) + extra = 10 + ctx.prec * nd // 10 + if z: + M = ctx.mag(z) + if M > 5 or (n != 1 and M < -5): + extra += 2*abs(M) + cz = 0.5 + extra2 = 50 + prec0 = ctx.prec + try: + ctx.prec += extra + if n == 1: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._djacobi_theta2(z - ctx.pi/2, q, nd) + else: + ctx.dps += 10 + res = ctx._djacobi_theta2a(z - ctx.pi/2, q, nd) + else: + res = ctx._djacobi_theta2(z - ctx.pi/2, q, nd) + elif n == 2: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._djacobi_theta2(z, q, nd) + else: + ctx.dps += 10 + res = ctx._djacobi_theta2a(z, q, nd) + else: + res = ctx._djacobi_theta2(z, q, nd) + elif n == 3: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._djacobi_theta3(z, q, nd) + else: + ctx.dps += 10 + res = ctx._djacobi_theta3a(z, q, nd) + else: + res = ctx._djacobi_theta3(z, q, nd) + elif n == 4: + if ctx._im(z): + if abs(ctx._im(z)) < cz * abs(ctx._re(ctx.log(q))): + ctx.dps += extra2 + res = ctx._djacobi_theta3(z, -q, nd) + else: + ctx.dps += 10 + res = ctx._djacobi_theta3a(z, -q, nd) + else: + res = ctx._djacobi_theta3(z, -q, nd) + else: + raise ValueError + finally: + ctx.prec = prec0 + return +res diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zeta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zeta.py new file mode 100644 index 0000000000000000000000000000000000000000..d7ede50d95e5b6eff511619620c934529942cbdd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zeta.py @@ -0,0 +1,1154 @@ +from __future__ import print_function + +from ..libmp.backend import xrange +from .functions import defun, defun_wrapped, defun_static + +@defun +def stieltjes(ctx, n, a=1): + n = ctx.convert(n) + a = ctx.convert(a) + if n < 0: + return ctx.bad_domain("Stieltjes constants defined for n >= 0") + if hasattr(ctx, "stieltjes_cache"): + stieltjes_cache = ctx.stieltjes_cache + else: + stieltjes_cache = ctx.stieltjes_cache = {} + if a == 1: + if n == 0: + return +ctx.euler + if n in stieltjes_cache: + prec, s = stieltjes_cache[n] + if prec >= ctx.prec: + return +s + mag = 1 + def f(x): + xa = x/a + v = (xa-ctx.j)*ctx.ln(a-ctx.j*x)**n/(1+xa**2)/(ctx.exp(2*ctx.pi*x)-1) + return ctx._re(v) / mag + orig = ctx.prec + try: + # Normalize integrand by approx. magnitude to + # speed up quadrature (which uses absolute error) + if n > 50: + ctx.prec = 20 + mag = ctx.quad(f, [0,ctx.inf], maxdegree=3) + ctx.prec = orig + 10 + int(n**0.5) + s = ctx.quad(f, [0,ctx.inf], maxdegree=20) + v = ctx.ln(a)**n/(2*a) - ctx.ln(a)**(n+1)/(n+1) + 2*s/a*mag + finally: + ctx.prec = orig + if a == 1 and ctx.isint(n): + stieltjes_cache[n] = (ctx.prec, v) + return +v + +@defun_wrapped +def siegeltheta(ctx, t, derivative=0): + d = int(derivative) + if (t == ctx.inf or t == ctx.ninf): + if d < 2: + if t == ctx.ninf and d == 0: + return ctx.ninf + return ctx.inf + else: + return ctx.zero + if d == 0: + if ctx._im(t): + # XXX: cancellation occurs + a = ctx.loggamma(0.25+0.5j*t) + b = ctx.loggamma(0.25-0.5j*t) + return -ctx.ln(ctx.pi)/2*t - 0.5j*(a-b) + else: + if ctx.isinf(t): + return t + return ctx._im(ctx.loggamma(0.25+0.5j*t)) - ctx.ln(ctx.pi)/2*t + if d > 0: + a = (-0.5j)**(d-1)*ctx.polygamma(d-1, 0.25-0.5j*t) + b = (0.5j)**(d-1)*ctx.polygamma(d-1, 0.25+0.5j*t) + if ctx._im(t): + if d == 1: + return -0.5*ctx.log(ctx.pi)+0.25*(a+b) + else: + return 0.25*(a+b) + else: + if d == 1: + return ctx._re(-0.5*ctx.log(ctx.pi)+0.25*(a+b)) + else: + return ctx._re(0.25*(a+b)) + +@defun_wrapped +def grampoint(ctx, n): + # asymptotic expansion, from + # http://mathworld.wolfram.com/GramPoint.html + g = 2*ctx.pi*ctx.exp(1+ctx.lambertw((8*n+1)/(8*ctx.e))) + return ctx.findroot(lambda t: ctx.siegeltheta(t)-ctx.pi*n, g) + + +@defun_wrapped +def siegelz(ctx, t, **kwargs): + d = int(kwargs.get("derivative", 0)) + t = ctx.convert(t) + t1 = ctx._re(t) + t2 = ctx._im(t) + prec = ctx.prec + try: + if abs(t1) > 500*prec and t2**2 < t1: + v = ctx.rs_z(t, d) + if ctx._is_real_type(t): + return ctx._re(v) + return v + except NotImplementedError: + pass + ctx.prec += 21 + e1 = ctx.expj(ctx.siegeltheta(t)) + z = ctx.zeta(0.5+ctx.j*t) + if d == 0: + v = e1*z + ctx.prec=prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z1 = ctx.zeta(0.5+ctx.j*t, derivative=1) + theta1 = ctx.siegeltheta(t, derivative=1) + if d == 1: + v = ctx.j*e1*(z1+z*theta1) + ctx.prec=prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z2 = ctx.zeta(0.5+ctx.j*t, derivative=2) + theta2 = ctx.siegeltheta(t, derivative=2) + comb1 = theta1**2-ctx.j*theta2 + if d == 2: + def terms(): + return [2*z1*theta1, z2, z*comb1] + v = ctx.sum_accurately(terms, 1) + v = -e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + ctx.prec += 10 + z3 = ctx.zeta(0.5+ctx.j*t, derivative=3) + theta3 = ctx.siegeltheta(t, derivative=3) + comb2 = theta1**3-3*ctx.j*theta1*theta2-theta3 + if d == 3: + def terms(): + return [3*theta1*z2, 3*z1*comb1, z3+z*comb2] + v = ctx.sum_accurately(terms, 1) + v = -ctx.j*e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + z4 = ctx.zeta(0.5+ctx.j*t, derivative=4) + theta4 = ctx.siegeltheta(t, derivative=4) + def terms(): + return [theta1**4, -6*ctx.j*theta1**2*theta2, -3*theta2**2, + -4*theta1*theta3, ctx.j*theta4] + comb3 = ctx.sum_accurately(terms, 1) + if d == 4: + def terms(): + return [6*theta1**2*z2, -6*ctx.j*z2*theta2, 4*theta1*z3, + 4*z1*comb2, z4, z*comb3] + v = ctx.sum_accurately(terms, 1) + v = e1*v + ctx.prec = prec + if ctx._is_real_type(t): + return ctx._re(v) + return +v + if d > 4: + h = lambda x: ctx.siegelz(x, derivative=4) + return ctx.diff(h, t, n=d-4) + + +_zeta_zeros = [ +14.134725142,21.022039639,25.010857580,30.424876126,32.935061588, +37.586178159,40.918719012,43.327073281,48.005150881,49.773832478, +52.970321478,56.446247697,59.347044003,60.831778525,65.112544048, +67.079810529,69.546401711,72.067157674,75.704690699,77.144840069, +79.337375020,82.910380854,84.735492981,87.425274613,88.809111208, +92.491899271,94.651344041,95.870634228,98.831194218,101.317851006, +103.725538040,105.446623052,107.168611184,111.029535543,111.874659177, +114.320220915,116.226680321,118.790782866,121.370125002,122.946829294, +124.256818554,127.516683880,129.578704200,131.087688531,133.497737203, +134.756509753,138.116042055,139.736208952,141.123707404,143.111845808, +146.000982487,147.422765343,150.053520421,150.925257612,153.024693811, +156.112909294,157.597591818,158.849988171,161.188964138,163.030709687, +165.537069188,167.184439978,169.094515416,169.911976479,173.411536520, +174.754191523,176.441434298,178.377407776,179.916484020,182.207078484, +184.874467848,185.598783678,187.228922584,189.416158656,192.026656361, +193.079726604,195.265396680,196.876481841,198.015309676,201.264751944, +202.493594514,204.189671803,205.394697202,207.906258888,209.576509717, +211.690862595,213.347919360,214.547044783,216.169538508,219.067596349, +220.714918839,221.430705555,224.007000255,224.983324670,227.421444280, +229.337413306,231.250188700,231.987235253,233.693404179,236.524229666, +] + +def _load_zeta_zeros(url): + import urllib + d = urllib.urlopen(url) + L = [float(x) for x in d.readlines()] + # Sanity check + assert round(L[0]) == 14 + _zeta_zeros[:] = L + +@defun +def oldzetazero(ctx, n, url='http://www.dtc.umn.edu/~odlyzko/zeta_tables/zeros1'): + n = int(n) + if n < 0: + return ctx.zetazero(-n).conjugate() + if n == 0: + raise ValueError("n must be nonzero") + if n > len(_zeta_zeros) and n <= 100000: + _load_zeta_zeros(url) + if n > len(_zeta_zeros): + raise NotImplementedError("n too large for zetazeros") + return ctx.mpc(0.5, ctx.findroot(ctx.siegelz, _zeta_zeros[n-1])) + +@defun_wrapped +def riemannr(ctx, x): + if x == 0: + return ctx.zero + # Check if a simple asymptotic estimate is accurate enough + if abs(x) > 1000: + a = ctx.li(x) + b = 0.5*ctx.li(ctx.sqrt(x)) + if abs(b) < abs(a)*ctx.eps: + return a + if abs(x) < 0.01: + # XXX + ctx.prec += int(-ctx.log(abs(x),2)) + # Sum Gram's series + s = t = ctx.one + u = ctx.ln(x) + k = 1 + while abs(t) > abs(s)*ctx.eps: + t = t * u / k + s += t / (k * ctx._zeta_int(k+1)) + k += 1 + return s + +@defun_static +def primepi(ctx, x): + x = int(x) + if x < 2: + return 0 + return len(ctx.list_primes(x)) + +# TODO: fix the interface wrt contexts +@defun_wrapped +def primepi2(ctx, x): + x = int(x) + if x < 2: + return ctx._iv.zero + if x < 2657: + return ctx._iv.mpf(ctx.primepi(x)) + mid = ctx.li(x) + # Schoenfeld's estimate for x >= 2657, assuming RH + err = ctx.sqrt(x,rounding='u')*ctx.ln(x,rounding='u')/8/ctx.pi(rounding='d') + a = ctx.floor((ctx._iv.mpf(mid)-err).a, rounding='d') + b = ctx.ceil((ctx._iv.mpf(mid)+err).b, rounding='u') + return ctx._iv.mpf([a,b]) + +@defun_wrapped +def primezeta(ctx, s): + if ctx.isnan(s): + return s + if ctx.re(s) <= 0: + raise ValueError("prime zeta function defined only for re(s) > 0") + if s == 1: + return ctx.inf + if s == 0.5: + return ctx.mpc(ctx.ninf, ctx.pi) + r = ctx.re(s) + if r > ctx.prec: + return 0.5**s + else: + wp = ctx.prec + int(r) + def terms(): + orig = ctx.prec + # zeta ~ 1+eps; need to set precision + # to get logarithm accurately + k = 0 + while 1: + k += 1 + u = ctx.moebius(k) + if not u: + continue + ctx.prec = wp + t = u*ctx.ln(ctx.zeta(k*s))/k + if not t: + return + #print ctx.prec, ctx.nstr(t) + ctx.prec = orig + yield t + return ctx.sum_accurately(terms) + +# TODO: for bernpoly and eulerpoly, ensure that all exact zeros are covered + +@defun_wrapped +def bernpoly(ctx, n, z): + # Slow implementation: + #return sum(ctx.binomial(n,k)*ctx.bernoulli(k)*z**(n-k) for k in xrange(0,n+1)) + n = int(n) + if n < 0: + raise ValueError("Bernoulli polynomials only defined for n >= 0") + if z == 0 or (z == 1 and n > 1): + return ctx.bernoulli(n) + if z == 0.5: + return (ctx.ldexp(1,1-n)-1)*ctx.bernoulli(n) + if n <= 3: + if n == 0: return z ** 0 + if n == 1: return z - 0.5 + if n == 2: return (6*z*(z-1)+1)/6 + if n == 3: return z*(z*(z-1.5)+0.5) + if ctx.isinf(z): + return z ** n + if ctx.isnan(z): + return z + if abs(z) > 2: + def terms(): + t = ctx.one + yield t + r = ctx.one/z + k = 1 + while k <= n: + t = t*(n+1-k)/k*r + if not (k > 2 and k & 1): + yield t*ctx.bernoulli(k) + k += 1 + return ctx.sum_accurately(terms) * z**n + else: + def terms(): + yield ctx.bernoulli(n) + t = ctx.one + k = 1 + while k <= n: + t = t*(n+1-k)/k * z + m = n-k + if not (m > 2 and m & 1): + yield t*ctx.bernoulli(m) + k += 1 + return ctx.sum_accurately(terms) + +@defun_wrapped +def eulerpoly(ctx, n, z): + n = int(n) + if n < 0: + raise ValueError("Euler polynomials only defined for n >= 0") + if n <= 2: + if n == 0: return z ** 0 + if n == 1: return z - 0.5 + if n == 2: return z*(z-1) + if ctx.isinf(z): + return z**n + if ctx.isnan(z): + return z + m = n+1 + if z == 0: + return -2*(ctx.ldexp(1,m)-1)*ctx.bernoulli(m)/m * z**0 + if z == 1: + return 2*(ctx.ldexp(1,m)-1)*ctx.bernoulli(m)/m * z**0 + if z == 0.5: + if n % 2: + return ctx.zero + # Use exact code for Euler numbers + if n < 100 or n*ctx.mag(0.46839865*n) < ctx.prec*0.25: + return ctx.ldexp(ctx._eulernum(n), -n) + # http://functions.wolfram.com/Polynomials/EulerE2/06/01/02/01/0002/ + def terms(): + t = ctx.one + k = 0 + w = ctx.ldexp(1,n+2) + while 1: + v = n-k+1 + if not (v > 2 and v & 1): + yield (2-w)*ctx.bernoulli(v)*t + k += 1 + if k > n: + break + t = t*z*(n-k+2)/k + w *= 0.5 + return ctx.sum_accurately(terms) / m + +@defun +def eulernum(ctx, n, exact=False): + n = int(n) + if exact: + return int(ctx._eulernum(n)) + if n < 100: + return ctx.mpf(ctx._eulernum(n)) + if n % 2: + return ctx.zero + return ctx.ldexp(ctx.eulerpoly(n,0.5), n) + +# TODO: this should be implemented low-level +def polylog_series(ctx, s, z): + tol = +ctx.eps + l = ctx.zero + k = 1 + zk = z + while 1: + term = zk / k**s + l += term + if abs(term) < tol: + break + zk *= z + k += 1 + return l + +def polylog_continuation(ctx, n, z): + if n < 0: + return z*0 + twopij = 2j * ctx.pi + a = -twopij**n/ctx.fac(n) * ctx.bernpoly(n, ctx.ln(z)/twopij) + if ctx._is_real_type(z) and z < 0: + a = ctx._re(a) + if ctx._im(z) < 0 or (ctx._im(z) == 0 and ctx._re(z) >= 1): + a -= twopij*ctx.ln(z)**(n-1)/ctx.fac(n-1) + return a + +def polylog_unitcircle(ctx, n, z): + tol = +ctx.eps + if n > 1: + l = ctx.zero + logz = ctx.ln(z) + logmz = ctx.one + m = 0 + while 1: + if (n-m) != 1: + term = ctx.zeta(n-m) * logmz / ctx.fac(m) + if term and abs(term) < tol: + break + l += term + logmz *= logz + m += 1 + l += ctx.ln(z)**(n-1)/ctx.fac(n-1)*(ctx.harmonic(n-1)-ctx.ln(-ctx.ln(z))) + elif n < 1: # else + l = ctx.fac(-n)*(-ctx.ln(z))**(n-1) + logz = ctx.ln(z) + logkz = ctx.one + k = 0 + while 1: + b = ctx.bernoulli(k-n+1) + if b: + term = b*logkz/(ctx.fac(k)*(k-n+1)) + if abs(term) < tol: + break + l -= term + logkz *= logz + k += 1 + else: + raise ValueError + if ctx._is_real_type(z) and z < 0: + l = ctx._re(l) + return l + +def polylog_general(ctx, s, z): + v = ctx.zero + u = ctx.ln(z) + if not abs(u) < 5: # theoretically |u| < 2*pi + j = ctx.j + v = 1-s + y = ctx.ln(-z)/(2*ctx.pi*j) + return ctx.gamma(v)*(j**v*ctx.zeta(v,0.5+y) + j**-v*ctx.zeta(v,0.5-y))/(2*ctx.pi)**v + t = 1 + k = 0 + while 1: + term = ctx.zeta(s-k) * t + if abs(term) < ctx.eps: + break + v += term + k += 1 + t *= u + t /= k + return ctx.gamma(1-s)*(-u)**(s-1) + v + +@defun_wrapped +def polylog(ctx, s, z): + s = ctx.convert(s) + z = ctx.convert(z) + if z == 1: + return ctx.zeta(s) + if z == -1: + return -ctx.altzeta(s) + if s == 0: + return z/(1-z) + if s == 1: + return -ctx.ln(1-z) + if s == -1: + return z/(1-z)**2 + if abs(z) <= 0.75 or (not ctx.isint(s) and abs(z) < 0.9): + return polylog_series(ctx, s, z) + if abs(z) >= 1.4 and ctx.isint(s): + return (-1)**(s+1)*polylog_series(ctx, s, 1/z) + polylog_continuation(ctx, int(ctx.re(s)), z) + if ctx.isint(s): + return polylog_unitcircle(ctx, int(ctx.re(s)), z) + return polylog_general(ctx, s, z) + +@defun_wrapped +def clsin(ctx, s, z, pi=False): + if ctx.isint(s) and s < 0 and int(s) % 2 == 1: + return z*0 + if pi: + a = ctx.expjpi(z) + else: + a = ctx.expj(z) + if ctx._is_real_type(z) and ctx._is_real_type(s): + return ctx.im(ctx.polylog(s,a)) + b = 1/a + return (-0.5j)*(ctx.polylog(s,a) - ctx.polylog(s,b)) + +@defun_wrapped +def clcos(ctx, s, z, pi=False): + if ctx.isint(s) and s < 0 and int(s) % 2 == 0: + return z*0 + if pi: + a = ctx.expjpi(z) + else: + a = ctx.expj(z) + if ctx._is_real_type(z) and ctx._is_real_type(s): + return ctx.re(ctx.polylog(s,a)) + b = 1/a + return 0.5*(ctx.polylog(s,a) + ctx.polylog(s,b)) + +@defun +def altzeta(ctx, s, **kwargs): + try: + return ctx._altzeta(s, **kwargs) + except NotImplementedError: + return ctx._altzeta_generic(s) + +@defun_wrapped +def _altzeta_generic(ctx, s): + if s == 1: + return ctx.ln2 + 0*s + return -ctx.powm1(2, 1-s) * ctx.zeta(s) + +@defun +def zeta(ctx, s, a=1, derivative=0, method=None, **kwargs): + d = int(derivative) + if a == 1 and not (d or method): + try: + return ctx._zeta(s, **kwargs) + except NotImplementedError: + pass + s = ctx.convert(s) + prec = ctx.prec + method = kwargs.get('method') + verbose = kwargs.get('verbose') + if (not s) and (not derivative): + return ctx.mpf(0.5) - ctx._convert_param(a)[0] + if a == 1 and method != 'euler-maclaurin': + im = abs(ctx._im(s)) + re = abs(ctx._re(s)) + #if (im < prec or method == 'borwein') and not derivative: + # try: + # if verbose: + # print "zeta: Attempting to use the Borwein algorithm" + # return ctx._zeta(s, **kwargs) + # except NotImplementedError: + # if verbose: + # print "zeta: Could not use the Borwein algorithm" + # pass + if abs(im) > 500*prec and 10*re < prec and derivative <= 4 or \ + method == 'riemann-siegel': + try: # py2.4 compatible try block + try: + if verbose: + print("zeta: Attempting to use the Riemann-Siegel algorithm") + return ctx.rs_zeta(s, derivative, **kwargs) + except NotImplementedError: + if verbose: + print("zeta: Could not use the Riemann-Siegel algorithm") + pass + finally: + ctx.prec = prec + if s == 1: + return ctx.inf + abss = abs(s) + if abss == ctx.inf: + if ctx.re(s) == ctx.inf: + if d == 0: + return ctx.one + return ctx.zero + return s*0 + elif ctx.isnan(abss): + return 1/s + if ctx.re(s) > 2*ctx.prec and a == 1 and not derivative: + return ctx.one + ctx.power(2, -s) + return +ctx._hurwitz(s, a, d, **kwargs) + +@defun +def _hurwitz(ctx, s, a=1, d=0, **kwargs): + prec = ctx.prec + verbose = kwargs.get('verbose') + try: + extraprec = 10 + ctx.prec += extraprec + # We strongly want to special-case rational a + a, atype = ctx._convert_param(a) + if ctx.re(s) < 0: + if verbose: + print("zeta: Attempting reflection formula") + try: + return _hurwitz_reflection(ctx, s, a, d, atype) + except NotImplementedError: + pass + if verbose: + print("zeta: Reflection formula failed") + if verbose: + print("zeta: Using the Euler-Maclaurin algorithm") + while 1: + ctx.prec = prec + extraprec + T1, T2 = _hurwitz_em(ctx, s, a, d, prec+10, verbose) + cancellation = ctx.mag(T1) - ctx.mag(T1+T2) + if verbose: + print("Term 1:", T1) + print("Term 2:", T2) + print("Cancellation:", cancellation, "bits") + if cancellation < extraprec: + return T1 + T2 + else: + extraprec = max(2*extraprec, min(cancellation + 5, 100*prec)) + if extraprec > kwargs.get('maxprec', 100*prec): + raise ctx.NoConvergence("zeta: too much cancellation") + finally: + ctx.prec = prec + +def _hurwitz_reflection(ctx, s, a, d, atype): + # TODO: implement for derivatives + if d != 0: + raise NotImplementedError + res = ctx.re(s) + negs = -s + # Integer reflection formula + if ctx.isnpint(s): + n = int(res) + if n <= 0: + return ctx.bernpoly(1-n, a) / (n-1) + if not (atype == 'Q' or atype == 'Z'): + raise NotImplementedError + t = 1-s + # We now require a to be standardized + v = 0 + shift = 0 + b = a + while ctx.re(b) > 1: + b -= 1 + v -= b**negs + shift -= 1 + while ctx.re(b) <= 0: + v += b**negs + b += 1 + shift += 1 + # Rational reflection formula + try: + p, q = a._mpq_ + except: + assert a == int(a) + p = int(a) + q = 1 + p += shift*q + assert 1 <= p <= q + g = ctx.fsum(ctx.cospi(t/2-2*k*b)*ctx._hurwitz(t,(k,q)) \ + for k in range(1,q+1)) + g *= 2*ctx.gamma(t)/(2*ctx.pi*q)**t + v += g + return v + +def _hurwitz_em(ctx, s, a, d, prec, verbose): + # May not be converted at this point + a = ctx.convert(a) + tol = -prec + # Estimate number of terms for Euler-Maclaurin summation; could be improved + M1 = 0 + M2 = prec // 3 + N = M2 + lsum = 0 + # This speeds up the recurrence for derivatives + if ctx.isint(s): + s = int(ctx._re(s)) + s1 = s-1 + while 1: + # Truncated L-series + l = ctx._zetasum(s, M1+a, M2-M1-1, [d])[0][0] + #if d: + # l = ctx.fsum((-ctx.ln(n+a))**d * (n+a)**negs for n in range(M1,M2)) + #else: + # l = ctx.fsum((n+a)**negs for n in range(M1,M2)) + lsum += l + M2a = M2+a + logM2a = ctx.ln(M2a) + logM2ad = logM2a**d + logs = [logM2ad] + logr = 1/logM2a + rM2a = 1/M2a + M2as = M2a**(-s) + if d: + tailsum = ctx.gammainc(d+1, s1*logM2a) / s1**(d+1) + else: + tailsum = 1/((s1)*(M2a)**s1) + tailsum += 0.5 * logM2ad * M2as + U = [1] + r = M2as + fact = 2 + for j in range(1, N+1): + # TODO: the following could perhaps be tidied a bit + j2 = 2*j + if j == 1: + upds = [1] + else: + upds = [j2-2, j2-1] + for m in upds: + D = min(m,d+1) + if m <= d: + logs.append(logs[-1] * logr) + Un = [0]*(D+1) + for i in xrange(D): Un[i] = (1-m-s)*U[i] + for i in xrange(1,D+1): Un[i] += (d-(i-1))*U[i-1] + U = Un + r *= rM2a + t = ctx.fdot(U, logs) * r * ctx.bernoulli(j2)/(-fact) + tailsum += t + if ctx.mag(t) < tol: + return lsum, (-1)**d * tailsum + fact *= (j2+1)*(j2+2) + if verbose: + print("Sum range:", M1, M2, "term magnitude", ctx.mag(t), "tolerance", tol) + M1, M2 = M2, M2*2 + if ctx.re(s) < 0: + N += N//2 + + + +@defun +def _zetasum(ctx, s, a, n, derivatives=[0], reflect=False): + """ + Returns [xd0,xd1,...,xdr], [yd0,yd1,...ydr] where + + xdk = D^k ( 1/a^s + 1/(a+1)^s + ... + 1/(a+n)^s ) + ydk = D^k conj( 1/a^(1-s) + 1/(a+1)^(1-s) + ... + 1/(a+n)^(1-s) ) + + D^k = kth derivative with respect to s, k ranges over the given list of + derivatives (which should consist of either a single element + or a range 0,1,...r). If reflect=False, the ydks are not computed. + """ + #print "zetasum", s, a, n + # don't use the fixed-point code if there are large exponentials + if abs(ctx.re(s)) < 0.5 * ctx.prec: + try: + return ctx._zetasum_fast(s, a, n, derivatives, reflect) + except NotImplementedError: + pass + negs = ctx.fneg(s, exact=True) + have_derivatives = derivatives != [0] + have_one_derivative = len(derivatives) == 1 + if not reflect: + if not have_derivatives: + return [ctx.fsum((a+k)**negs for k in xrange(n+1))], [] + if have_one_derivative: + d = derivatives[0] + x = ctx.fsum(ctx.ln(a+k)**d * (a+k)**negs for k in xrange(n+1)) + return [(-1)**d * x], [] + maxd = max(derivatives) + if not have_one_derivative: + derivatives = range(maxd+1) + xs = [ctx.zero for d in derivatives] + if reflect: + ys = [ctx.zero for d in derivatives] + else: + ys = [] + for k in xrange(n+1): + w = a + k + xterm = w ** negs + if reflect: + yterm = ctx.conj(ctx.one / (w * xterm)) + if have_derivatives: + logw = -ctx.ln(w) + if have_one_derivative: + logw = logw ** maxd + xs[0] += xterm * logw + if reflect: + ys[0] += yterm * logw + else: + t = ctx.one + for d in derivatives: + xs[d] += xterm * t + if reflect: + ys[d] += yterm * t + t *= logw + else: + xs[0] += xterm + if reflect: + ys[0] += yterm + return xs, ys + +@defun +def dirichlet(ctx, s, chi=[1], derivative=0): + s = ctx.convert(s) + q = len(chi) + d = int(derivative) + if d > 2: + raise NotImplementedError("arbitrary order derivatives") + prec = ctx.prec + try: + ctx.prec += 10 + if s == 1: + have_pole = True + for x in chi: + if x and x != 1: + have_pole = False + h = +ctx.eps + ctx.prec *= 2*(d+1) + s += h + if have_pole: + return +ctx.inf + z = ctx.zero + for p in range(1,q+1): + if chi[p%q]: + if d == 1: + z += chi[p%q] * (ctx.zeta(s, (p,q), 1) - \ + ctx.zeta(s, (p,q))*ctx.log(q)) + else: + z += chi[p%q] * ctx.zeta(s, (p,q)) + z /= q**s + finally: + ctx.prec = prec + return +z + + +def secondzeta_main_term(ctx, s, a, **kwargs): + tol = ctx.eps + f = lambda n: ctx.gammainc(0.5*s, a*gamm**2, regularized=True)*gamm**(-s) + totsum = term = ctx.zero + mg = ctx.inf + n = 0 + while mg > tol: + totsum += term + n += 1 + gamm = ctx.im(ctx.zetazero_memoized(n)) + term = f(n) + mg = abs(term) + err = 0 + if kwargs.get("error"): + sg = ctx.re(s) + err = 0.5*ctx.pi**(-1)*max(1,sg)*a**(sg-0.5)*ctx.log(gamm/(2*ctx.pi))*\ + ctx.gammainc(-0.5, a*gamm**2)/abs(ctx.gamma(s/2)) + err = abs(err) + return +totsum, err, n + +def secondzeta_prime_term(ctx, s, a, **kwargs): + tol = ctx.eps + f = lambda n: ctx.gammainc(0.5*(1-s),0.25*ctx.log(n)**2 * a**(-1))*\ + ((0.5*ctx.log(n))**(s-1))*ctx.mangoldt(n)/ctx.sqrt(n)/\ + (2*ctx.gamma(0.5*s)*ctx.sqrt(ctx.pi)) + totsum = term = ctx.zero + mg = ctx.inf + n = 1 + while mg > tol or n < 9: + totsum += term + n += 1 + term = f(n) + if term == 0: + mg = ctx.inf + else: + mg = abs(term) + if kwargs.get("error"): + err = mg + return +totsum, err, n + +def secondzeta_exp_term(ctx, s, a): + if ctx.isint(s) and ctx.re(s) <= 0: + m = int(round(ctx.re(s))) + if not m & 1: + return ctx.mpf('-0.25')**(-m//2) + tol = ctx.eps + f = lambda n: (0.25*a)**n/((n+0.5*s)*ctx.fac(n)) + totsum = ctx.zero + term = f(0) + mg = ctx.inf + n = 0 + while mg > tol: + totsum += term + n += 1 + term = f(n) + mg = abs(term) + v = a**(0.5*s)*totsum/ctx.gamma(0.5*s) + return v + +def secondzeta_singular_term(ctx, s, a, **kwargs): + factor = a**(0.5*(s-1))/(4*ctx.sqrt(ctx.pi)*ctx.gamma(0.5*s)) + extraprec = ctx.mag(factor) + ctx.prec += extraprec + factor = a**(0.5*(s-1))/(4*ctx.sqrt(ctx.pi)*ctx.gamma(0.5*s)) + tol = ctx.eps + f = lambda n: ctx.bernpoly(n,0.75)*(4*ctx.sqrt(a))**n*\ + ctx.gamma(0.5*n)/((s+n-1)*ctx.fac(n)) + totsum = ctx.zero + mg1 = ctx.inf + n = 1 + term = f(n) + mg2 = abs(term) + while mg2 > tol and mg2 <= mg1: + totsum += term + n += 1 + term = f(n) + totsum += term + n +=1 + term = f(n) + mg1 = mg2 + mg2 = abs(term) + totsum += term + pole = -2*(s-1)**(-2)+(ctx.euler+ctx.log(16*ctx.pi**2*a))*(s-1)**(-1) + st = factor*(pole+totsum) + err = 0 + if kwargs.get("error"): + if not ((mg2 > tol) and (mg2 <= mg1)): + if mg2 <= tol: + err = ctx.mpf(10)**int(ctx.log(abs(factor*tol),10)) + if mg2 > mg1: + err = ctx.mpf(10)**int(ctx.log(abs(factor*mg1),10)) + err = max(err, ctx.eps*1.) + ctx.prec -= extraprec + return +st, err + +@defun +def secondzeta(ctx, s, a = 0.015, **kwargs): + r""" + Evaluates the secondary zeta function `Z(s)`, defined for + `\mathrm{Re}(s)>1` by + + .. math :: + + Z(s) = \sum_{n=1}^{\infty} \frac{1}{\tau_n^s} + + where `\frac12+i\tau_n` runs through the zeros of `\zeta(s)` with + imaginary part positive. + + `Z(s)` extends to a meromorphic function on `\mathbb{C}` with a + double pole at `s=1` and simple poles at the points `-2n` for + `n=0`, 1, 2, ... + + **Examples** + + >>> from mpmath import * + >>> mp.pretty = True; mp.dps = 15 + >>> secondzeta(2) + 0.023104993115419 + >>> xi = lambda s: 0.5*s*(s-1)*pi**(-0.5*s)*gamma(0.5*s)*zeta(s) + >>> Xi = lambda t: xi(0.5+t*j) + >>> chop(-0.5*diff(Xi,0,n=2)/Xi(0)) + 0.023104993115419 + + We may ask for an approximate error value:: + + >>> secondzeta(0.5+100j, error=True) + ((-0.216272011276718 - 0.844952708937228j), 2.22044604925031e-16) + + The function has poles at the negative odd integers, + and dyadic rational values at the negative even integers:: + + >>> mp.dps = 30 + >>> secondzeta(-8) + -0.67236328125 + >>> secondzeta(-7) + +inf + + **Implementation notes** + + The function is computed as sum of four terms `Z(s)=A(s)-P(s)+E(s)-S(s)` + respectively main, prime, exponential and singular terms. + The main term `A(s)` is computed from the zeros of zeta. + The prime term depends on the von Mangoldt function. + The singular term is responsible for the poles of the function. + + The four terms depends on a small parameter `a`. We may change the + value of `a`. Theoretically this has no effect on the sum of the four + terms, but in practice may be important. + + A smaller value of the parameter `a` makes `A(s)` depend on + a smaller number of zeros of zeta, but `P(s)` uses more values of + von Mangoldt function. + + We may also add a verbose option to obtain data about the + values of the four terms. + + >>> mp.dps = 10 + >>> secondzeta(0.5 + 40j, error=True, verbose=True) + main term = (-30190318549.138656312556 - 13964804384.624622876523j) + computed using 19 zeros of zeta + prime term = (132717176.89212754625045 + 188980555.17563978290601j) + computed using 9 values of the von Mangoldt function + exponential term = (542447428666.07179812536 + 362434922978.80192435203j) + singular term = (512124392939.98154322355 + 348281138038.65531023921j) + ((0.059471043 + 0.3463514534j), 1.455191523e-11) + + >>> secondzeta(0.5 + 40j, a=0.04, error=True, verbose=True) + main term = (-151962888.19606243907725 - 217930683.90210294051982j) + computed using 9 zeros of zeta + prime term = (2476659342.3038722372461 + 28711581821.921627163136j) + computed using 37 values of the von Mangoldt function + exponential term = (178506047114.7838188264 + 819674143244.45677330576j) + singular term = (175877424884.22441310708 + 790744630738.28669174871j) + ((0.059471043 + 0.3463514534j), 1.455191523e-11) + + Notice the great cancellation between the four terms. Changing `a`, the + four terms are very different numbers but the cancellation gives + the good value of Z(s). + + **References** + + A. Voros, Zeta functions for the Riemann zeros, Ann. Institute Fourier, + 53, (2003) 665--699. + + A. Voros, Zeta functions over Zeros of Zeta Functions, Lecture Notes + of the Unione Matematica Italiana, Springer, 2009. + """ + s = ctx.convert(s) + a = ctx.convert(a) + tol = ctx.eps + if ctx.isint(s) and ctx.re(s) <= 1: + if abs(s-1) < tol*1000: + return ctx.inf + m = int(round(ctx.re(s))) + if m & 1: + return ctx.inf + else: + return ((-1)**(-m//2)*\ + ctx.fraction(8-ctx.eulernum(-m,exact=True),2**(-m+3))) + prec = ctx.prec + try: + t3 = secondzeta_exp_term(ctx, s, a) + extraprec = max(ctx.mag(t3),0) + ctx.prec += extraprec + 3 + t1, r1, gt = secondzeta_main_term(ctx,s,a,error='True', verbose='True') + t2, r2, pt = secondzeta_prime_term(ctx,s,a,error='True', verbose='True') + t4, r4 = secondzeta_singular_term(ctx,s,a,error='True') + t3 = secondzeta_exp_term(ctx, s, a) + err = r1+r2+r4 + t = t1-t2+t3-t4 + if kwargs.get("verbose"): + print('main term =', t1) + print(' computed using', gt, 'zeros of zeta') + print('prime term =', t2) + print(' computed using', pt, 'values of the von Mangoldt function') + print('exponential term =', t3) + print('singular term =', t4) + finally: + ctx.prec = prec + if kwargs.get("error"): + w = max(ctx.mag(abs(t)),0) + err = max(err*2**w, ctx.eps*1.*2**w) + return +t, err + return +t + + +@defun_wrapped +def lerchphi(ctx, z, s, a): + r""" + Gives the Lerch transcendent, defined for `|z| < 1` and + `\Re{a} > 0` by + + .. math :: + + \Phi(z,s,a) = \sum_{k=0}^{\infty} \frac{z^k}{(a+k)^s} + + and generally by the recurrence `\Phi(z,s,a) = z \Phi(z,s,a+1) + a^{-s}` + along with the integral representation valid for `\Re{a} > 0` + + .. math :: + + \Phi(z,s,a) = \frac{1}{2 a^s} + + \int_0^{\infty} \frac{z^t}{(a+t)^s} dt - + 2 \int_0^{\infty} \frac{\sin(t \log z - s + \operatorname{arctan}(t/a)}{(a^2 + t^2)^{s/2} + (e^{2 \pi t}-1)} dt. + + The Lerch transcendent generalizes the Hurwitz zeta function :func:`zeta` + (`z = 1`) and the polylogarithm :func:`polylog` (`a = 1`). + + **Examples** + + Several evaluations in terms of simpler functions:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> lerchphi(-1,2,0.5); 4*catalan + 3.663862376708876060218414 + 3.663862376708876060218414 + >>> diff(lerchphi, (-1,-2,1), (0,1,0)); 7*zeta(3)/(4*pi**2) + 0.2131391994087528954617607 + 0.2131391994087528954617607 + >>> lerchphi(-4,1,1); log(5)/4 + 0.4023594781085250936501898 + 0.4023594781085250936501898 + >>> lerchphi(-3+2j,1,0.5); 2*atanh(sqrt(-3+2j))/sqrt(-3+2j) + (1.142423447120257137774002 + 0.2118232380980201350495795j) + (1.142423447120257137774002 + 0.2118232380980201350495795j) + + Evaluation works for complex arguments and `|z| \ge 1`:: + + >>> lerchphi(1+2j, 3-j, 4+2j) + (0.002025009957009908600539469 + 0.003327897536813558807438089j) + >>> lerchphi(-2,2,-2.5) + -12.28676272353094275265944 + >>> lerchphi(10,10,10) + (-4.462130727102185701817349e-11 - 1.575172198981096218823481e-12j) + >>> lerchphi(10,10,-10.5) + (112658784011940.5605789002 - 498113185.5756221777743631j) + + Some degenerate cases:: + + >>> lerchphi(0,1,2) + 0.5 + >>> lerchphi(0,1,-2) + -0.5 + + Reduction to simpler functions:: + + >>> lerchphi(1, 4.25+1j, 1) + (1.044674457556746668033975 - 0.04674508654012658932271226j) + >>> zeta(4.25+1j) + (1.044674457556746668033975 - 0.04674508654012658932271226j) + >>> lerchphi(1 - 0.5**10, 4.25+1j, 1) + (1.044629338021507546737197 - 0.04667768813963388181708101j) + >>> lerchphi(3, 4, 1) + (1.249503297023366545192592 - 0.2314252413375664776474462j) + >>> polylog(4, 3) / 3 + (1.249503297023366545192592 - 0.2314252413375664776474462j) + >>> lerchphi(3, 4, 1 - 0.5**10) + (1.253978063946663945672674 - 0.2316736622836535468765376j) + + **References** + + 1. [DLMF]_ section 25.14 + + """ + if z == 0: + return a ** (-s) + # Faster, but these cases are useful for testing right now + if z == 1: + return ctx.zeta(s, a) + if a == 1: + return ctx.polylog(s, z) / z + if ctx.re(a) < 1: + if ctx.isnpint(a): + raise ValueError("Lerch transcendent complex infinity") + m = int(ctx.ceil(1-ctx.re(a))) + v = ctx.zero + zpow = ctx.one + for n in xrange(m): + v += zpow / (a+n)**s + zpow *= z + return zpow * ctx.lerchphi(z,s, a+m) + v + g = ctx.ln(z) + v = 1/(2*a**s) + ctx.gammainc(1-s, -a*g) * (-g)**(s-1) / z**a + h = s / 2 + r = 2*ctx.pi + f = lambda t: ctx.sin(s*ctx.atan(t/a)-t*g) / \ + ((a**2+t**2)**h * ctx.expm1(r*t)) + v += 2*ctx.quad(f, [0, ctx.inf]) + if not ctx.im(z) and not ctx.im(s) and not ctx.im(a) and ctx.re(z) < 1: + v = ctx.chop(v) + return v diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zetazeros.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zetazeros.py new file mode 100644 index 0000000000000000000000000000000000000000..37c11a29426b0114053ae61664541f7ae7de95d8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/functions/zetazeros.py @@ -0,0 +1,1018 @@ +""" +The function zetazero(n) computes the n-th nontrivial zero of zeta(s). + +The general strategy is to locate a block of Gram intervals B where we +know exactly the number of zeros contained and which of those zeros +is that which we search. + +If n <= 400 000 000 we know exactly the Rosser exceptions, contained +in a list in this file. Hence for n<=400 000 000 we simply +look at these list of exceptions. If our zero is implicated in one of +these exceptions we have our block B. In other case we simply locate +the good Rosser block containing our zero. + +For n > 400 000 000 we apply the method of Turing, as complemented by +Lehman, Brent and Trudgian to find a suitable B. +""" + +from .functions import defun, defun_wrapped + +def find_rosser_block_zero(ctx, n): + """for n<400 000 000 determines a block were one find our zero""" + for k in range(len(_ROSSER_EXCEPTIONS)//2): + a=_ROSSER_EXCEPTIONS[2*k][0] + b=_ROSSER_EXCEPTIONS[2*k][1] + if ((a<= n-2) and (n-1 <= b)): + t0 = ctx.grampoint(a) + t1 = ctx.grampoint(b) + v0 = ctx._fp.siegelz(t0) + v1 = ctx._fp.siegelz(t1) + my_zero_number = n-a-1 + zero_number_block = b-a + pattern = _ROSSER_EXCEPTIONS[2*k+1] + return (my_zero_number, [a,b], [t0,t1], [v0,v1]) + k = n-2 + t,v,b = compute_triple_tvb(ctx, k) + T = [t] + V = [v] + while b < 0: + k -= 1 + t,v,b = compute_triple_tvb(ctx, k) + T.insert(0,t) + V.insert(0,v) + my_zero_number = n-k-1 + m = n-1 + t,v,b = compute_triple_tvb(ctx, m) + T.append(t) + V.append(v) + while b < 0: + m += 1 + t,v,b = compute_triple_tvb(ctx, m) + T.append(t) + V.append(v) + return (my_zero_number, [k,m], T, V) + +def wpzeros(t): + """Precision needed to compute higher zeros""" + wp = 53 + if t > 3*10**8: + wp = 63 + if t > 10**11: + wp = 70 + if t > 10**14: + wp = 83 + return wp + +def separate_zeros_in_block(ctx, zero_number_block, T, V, limitloop=None, + fp_tolerance=None): + """Separate the zeros contained in the block T, limitloop + determines how long one must search""" + if limitloop is None: + limitloop = ctx.inf + loopnumber = 0 + variations = count_variations(V) + while ((variations < zero_number_block) and (loopnumber 0): + alpha = ctx.sqrt(u/v) + b= (alpha*a+b2)/(alpha+1) + else: + b = (a+b2)/2 + if fp_tolerance < 10: + w = ctx._fp.siegelz(b) + if abs(w)ITERATION_LIMIT)and(loopnumber>2)and(variations+2==zero_number_block): + dtMax=0 + dtSec=0 + kMax = 0 + for k1 in range(1,len(T)): + dt = T[k1]-T[k1-1] + if dt > dtMax: + kMax=k1 + dtSec = dtMax + dtMax = dt + elif (dtdtSec): + dtSec = dt + if dtMax>3*dtSec: + f = lambda x: ctx.rs_z(x,derivative=1) + t0=T[kMax-1] + t1 = T[kMax] + t=ctx.findroot(f, (t0,t1), solver ='illinois',verify=False, verbose=False) + v = ctx.siegelz(t) + if (t0 2*wpz: + index +=1 + precs = [precs[0] // 2 +3+2*index] + precs + ctx.prec = precs[0] + guard + r = ctx.findroot(lambda x:ctx.siegelz(x), (t0,t1), solver ='illinois', verbose=False) + #print "first step at", ctx.dps, "digits" + z=ctx.mpc(0.5,r) + for prec in precs[1:]: + ctx.prec = prec + guard + #print "refining to", ctx.dps, "digits" + znew = z - ctx.zeta(z) / ctx.zeta(z, derivative=1) + #print "difference", ctx.nstr(abs(z-znew)) + z=ctx.mpc(0.5,ctx.im(znew)) + return ctx.im(z) + +def sure_number_block(ctx, n): + """The number of good Rosser blocks needed to apply + Turing method + References: + R. P. Brent, On the Zeros of the Riemann Zeta Function + in the Critical Strip, Math. Comp. 33 (1979) 1361--1372 + T. Trudgian, Improvements to Turing Method, Math. Comp.""" + if n < 9*10**5: + return(2) + g = ctx.grampoint(n-100) + lg = ctx._fp.ln(g) + brent = 0.0061 * lg**2 +0.08*lg + trudgian = 0.0031 * lg**2 +0.11*lg + N = ctx.ceil(min(brent,trudgian)) + N = int(N) + return N + +def compute_triple_tvb(ctx, n): + t = ctx.grampoint(n) + v = ctx._fp.siegelz(t) + if ctx.mag(abs(v))400 000 000""" + sb = sure_number_block(ctx, n) + number_goodblocks = 0 + m2 = n-1 + t, v, b = compute_triple_tvb(ctx, m2) + Tf = [t] + Vf = [v] + while b < 0: + m2 += 1 + t,v,b = compute_triple_tvb(ctx, m2) + Tf.append(t) + Vf.append(v) + goodpoints = [m2] + T = [t] + V = [v] + while number_goodblocks < 2*sb: + m2 += 1 + t, v, b = compute_triple_tvb(ctx, m2) + T.append(t) + V.append(v) + while b < 0: + m2 += 1 + t,v,b = compute_triple_tvb(ctx, m2) + T.append(t) + V.append(v) + goodpoints.append(m2) + zn = len(T)-1 + A, B, separated =\ + separate_zeros_in_block(ctx, zn, T, V, limitloop=ITERATION_LIMIT, + fp_tolerance=fp_tolerance) + Tf.pop() + Tf.extend(A) + Vf.pop() + Vf.extend(B) + if separated: + number_goodblocks += 1 + else: + number_goodblocks = 0 + T = [t] + V = [v] + # Now the same procedure to the left + number_goodblocks = 0 + m2 = n-2 + t, v, b = compute_triple_tvb(ctx, m2) + Tf.insert(0,t) + Vf.insert(0,v) + while b < 0: + m2 -= 1 + t,v,b = compute_triple_tvb(ctx, m2) + Tf.insert(0,t) + Vf.insert(0,v) + goodpoints.insert(0,m2) + T = [t] + V = [v] + while number_goodblocks < 2*sb: + m2 -= 1 + t, v, b = compute_triple_tvb(ctx, m2) + T.insert(0,t) + V.insert(0,v) + while b < 0: + m2 -= 1 + t,v,b = compute_triple_tvb(ctx, m2) + T.insert(0,t) + V.insert(0,v) + goodpoints.insert(0,m2) + zn = len(T)-1 + A, B, separated =\ + separate_zeros_in_block(ctx, zn, T, V, limitloop=ITERATION_LIMIT, fp_tolerance=fp_tolerance) + A.pop() + Tf = A+Tf + B.pop() + Vf = B+Vf + if separated: + number_goodblocks += 1 + else: + number_goodblocks = 0 + T = [t] + V = [v] + r = goodpoints[2*sb] + lg = len(goodpoints) + s = goodpoints[lg-2*sb-1] + tr, vr, br = compute_triple_tvb(ctx, r) + ar = Tf.index(tr) + ts, vs, bs = compute_triple_tvb(ctx, s) + as1 = Tf.index(ts) + T = Tf[ar:as1+1] + V = Vf[ar:as1+1] + zn = s-r + A, B, separated =\ + separate_zeros_in_block(ctx, zn,T,V,limitloop=ITERATION_LIMIT, fp_tolerance=fp_tolerance) + if separated: + return (n-r-1,[r,s],A,B) + q = goodpoints[sb] + lg = len(goodpoints) + t = goodpoints[lg-sb-1] + tq, vq, bq = compute_triple_tvb(ctx, q) + aq = Tf.index(tq) + tt, vt, bt = compute_triple_tvb(ctx, t) + at = Tf.index(tt) + T = Tf[aq:at+1] + V = Vf[aq:at+1] + return (n-q-1,[q,t],T,V) + +def count_variations(V): + count = 0 + vold = V[0] + for n in range(1, len(V)): + vnew = V[n] + if vold*vnew < 0: + count +=1 + vold = vnew + return count + +def pattern_construct(ctx, block, T, V): + pattern = '(' + a = block[0] + b = block[1] + t0,v0,b0 = compute_triple_tvb(ctx, a) + k = 0 + k0 = 0 + for n in range(a+1,b+1): + t1,v1,b1 = compute_triple_tvb(ctx, n) + lgT =len(T) + while (k < lgT) and (T[k] <= t1): + k += 1 + L = V[k0:k] + L.append(v1) + L.insert(0,v0) + count = count_variations(L) + pattern = pattern + ("%s" % count) + if b1 > 0: + pattern = pattern + ')(' + k0 = k + t0,v0,b0 = t1,v1,b1 + pattern = pattern[:-1] + return pattern + +@defun +def zetazero(ctx, n, info=False, round=True): + r""" + Computes the `n`-th nontrivial zero of `\zeta(s)` on the critical line, + i.e. returns an approximation of the `n`-th largest complex number + `s = \frac{1}{2} + ti` for which `\zeta(s) = 0`. Equivalently, the + imaginary part `t` is a zero of the Z-function (:func:`~mpmath.siegelz`). + + **Examples** + + The first few zeros:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> zetazero(1) + (0.5 + 14.13472514173469379045725j) + >>> zetazero(2) + (0.5 + 21.02203963877155499262848j) + >>> zetazero(20) + (0.5 + 77.14484006887480537268266j) + + Verifying that the values are zeros:: + + >>> for n in range(1,5): + ... s = zetazero(n) + ... chop(zeta(s)), chop(siegelz(s.imag)) + ... + (0.0, 0.0) + (0.0, 0.0) + (0.0, 0.0) + (0.0, 0.0) + + Negative indices give the conjugate zeros (`n = 0` is undefined):: + + >>> zetazero(-1) + (0.5 - 14.13472514173469379045725j) + + :func:`~mpmath.zetazero` supports arbitrarily large `n` and arbitrary precision:: + + >>> mp.dps = 15 + >>> zetazero(1234567) + (0.5 + 727690.906948208j) + >>> mp.dps = 50 + >>> zetazero(1234567) + (0.5 + 727690.9069482075392389420041147142092708393819935j) + >>> chop(zeta(_)/_) + 0.0 + + with *info=True*, :func:`~mpmath.zetazero` gives additional information:: + + >>> mp.dps = 15 + >>> zetazero(542964976,info=True) + ((0.5 + 209039046.578535j), [542964969, 542964978], 6, '(013111110)') + + This means that the zero is between Gram points 542964969 and 542964978; + it is the 6-th zero between them. Finally (01311110) is the pattern + of zeros in this interval. The numbers indicate the number of zeros + in each Gram interval (Rosser blocks between parenthesis). In this case + there is only one Rosser block of length nine. + """ + n = int(n) + if n < 0: + return ctx.zetazero(-n).conjugate() + if n == 0: + raise ValueError("n must be nonzero") + wpinitial = ctx.prec + try: + wpz, fp_tolerance = comp_fp_tolerance(ctx, n) + ctx.prec = wpz + if n < 400000000: + my_zero_number, block, T, V =\ + find_rosser_block_zero(ctx, n) + else: + my_zero_number, block, T, V =\ + search_supergood_block(ctx, n, fp_tolerance) + zero_number_block = block[1]-block[0] + T, V, separated = separate_zeros_in_block(ctx, zero_number_block, T, V, + limitloop=ctx.inf, fp_tolerance=fp_tolerance) + if info: + pattern = pattern_construct(ctx,block,T,V) + prec = max(wpinitial, wpz) + t = separate_my_zero(ctx, my_zero_number, zero_number_block,T,V,prec) + v = ctx.mpc(0.5,t) + finally: + ctx.prec = wpinitial + if round: + v =+v + if info: + return (v,block,my_zero_number,pattern) + else: + return v + +def gram_index(ctx, t): + if t > 10**13: + wp = 3*ctx.log(t, 10) + else: + wp = 0 + prec = ctx.prec + try: + ctx.prec += wp + h = int(ctx.siegeltheta(t)/ctx.pi) + finally: + ctx.prec = prec + return(h) + +def count_to(ctx, t, T, V): + count = 0 + vold = V[0] + told = T[0] + tnew = T[1] + k = 1 + while tnew < t: + vnew = V[k] + if vold*vnew < 0: + count += 1 + vold = vnew + k += 1 + tnew = T[k] + a = ctx.siegelz(t) + if a*vold < 0: + count += 1 + return count + +def comp_fp_tolerance(ctx, n): + wpz = wpzeros(n*ctx.log(n)) + if n < 15*10**8: + fp_tolerance = 0.0005 + elif n <= 10**14: + fp_tolerance = 0.1 + else: + fp_tolerance = 100 + return wpz, fp_tolerance + +@defun +def nzeros(ctx, t): + r""" + Computes the number of zeros of the Riemann zeta function in + `(0,1) \times (0,t]`, usually denoted by `N(t)`. + + **Examples** + + The first zero has imaginary part between 14 and 15:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> nzeros(14) + 0 + >>> nzeros(15) + 1 + >>> zetazero(1) + (0.5 + 14.1347251417347j) + + Some closely spaced zeros:: + + >>> nzeros(10**7) + 21136125 + >>> zetazero(21136125) + (0.5 + 9999999.32718175j) + >>> zetazero(21136126) + (0.5 + 10000000.2400236j) + >>> nzeros(545439823.215) + 1500000001 + >>> zetazero(1500000001) + (0.5 + 545439823.201985j) + >>> zetazero(1500000002) + (0.5 + 545439823.325697j) + + This confirms the data given by J. van de Lune, + H. J. J. te Riele and D. T. Winter in 1986. + """ + if t < 14.1347251417347: + return 0 + x = gram_index(ctx, t) + k = int(ctx.floor(x)) + wpinitial = ctx.prec + wpz, fp_tolerance = comp_fp_tolerance(ctx, k) + ctx.prec = wpz + a = ctx.siegelz(t) + if k == -1 and a < 0: + return 0 + elif k == -1 and a > 0: + return 1 + if k+2 < 400000000: + Rblock = find_rosser_block_zero(ctx, k+2) + else: + Rblock = search_supergood_block(ctx, k+2, fp_tolerance) + n1, n2 = Rblock[1] + if n2-n1 == 1: + b = Rblock[3][0] + if a*b > 0: + ctx.prec = wpinitial + return k+1 + else: + ctx.prec = wpinitial + return k+2 + my_zero_number,block, T, V = Rblock + zero_number_block = n2-n1 + T, V, separated = separate_zeros_in_block(ctx,\ + zero_number_block, T, V,\ + limitloop=ctx.inf,\ + fp_tolerance=fp_tolerance) + n = count_to(ctx, t, T, V) + ctx.prec = wpinitial + return n+n1+1 + +@defun_wrapped +def backlunds(ctx, t): + r""" + Computes the function + `S(t) = \operatorname{arg} \zeta(\frac{1}{2} + it) / \pi`. + + See Titchmarsh Section 9.3 for details of the definition. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> backlunds(217.3) + 0.16302205431184 + + Generally, the value is a small number. At Gram points it is an integer, + frequently equal to 0:: + + >>> chop(backlunds(grampoint(200))) + 0.0 + >>> backlunds(extraprec(10)(grampoint)(211)) + 1.0 + >>> backlunds(extraprec(10)(grampoint)(232)) + -1.0 + + The number of zeros of the Riemann zeta function up to height `t` + satisfies `N(t) = \theta(t)/\pi + 1 + S(t)` (see :func:nzeros` and + :func:`siegeltheta`):: + + >>> t = 1234.55 + >>> nzeros(t) + 842 + >>> siegeltheta(t)/pi+1+backlunds(t) + 842.0 + + """ + return ctx.nzeros(t)-1-ctx.siegeltheta(t)/ctx.pi + + +""" +_ROSSER_EXCEPTIONS is a list of all exceptions to +Rosser's rule for n <= 400 000 000. + +Alternately the entry is of type [n,m], or a string. +The string is the zero pattern of the Block and the relevant +adjacent. For example (010)3 corresponds to a block +composed of three Gram intervals, the first ant third without +a zero and the intermediate with a zero. The next Gram interval +contain three zeros. So that in total we have 4 zeros in 4 Gram +blocks. n and m are the indices of the Gram points of this +interval of four Gram intervals. The Rosser exception is therefore +formed by the three Gram intervals that are signaled between +parenthesis. + +We have included also some Rosser's exceptions beyond n=400 000 000 +that are noted in the literature by some reason. + +The list is composed from the data published in the references: + +R. P. Brent, J. van de Lune, H. J. J. te Riele, D. T. Winter, +'On the Zeros of the Riemann Zeta Function in the Critical Strip. II', +Math. Comp. 39 (1982) 681--688. +See also Corrigenda in Math. Comp. 46 (1986) 771. + +J. van de Lune, H. J. J. te Riele, +'On the Zeros of the Riemann Zeta Function in the Critical Strip. III', +Math. Comp. 41 (1983) 759--767. +See also Corrigenda in Math. Comp. 46 (1986) 771. + +J. van de Lune, +'Sums of Equal Powers of Positive Integers', +Dissertation, +Vrije Universiteit te Amsterdam, Centrum voor Wiskunde en Informatica, +Amsterdam, 1984. + +Thanks to the authors all this papers and those others that have +contributed to make this possible. +""" + + + + + + + +_ROSSER_EXCEPTIONS = \ +[[13999525, 13999528], '(00)3', +[30783329, 30783332], '(00)3', +[30930926, 30930929], '3(00)', +[37592215, 37592218], '(00)3', +[40870156, 40870159], '(00)3', +[43628107, 43628110], '(00)3', +[46082042, 46082045], '(00)3', +[46875667, 46875670], '(00)3', +[49624540, 49624543], '3(00)', +[50799238, 50799241], '(00)3', +[55221453, 55221456], '3(00)', +[56948779, 56948782], '3(00)', +[60515663, 60515666], '(00)3', +[61331766, 61331770], '(00)40', +[69784843, 69784846], '3(00)', +[75052114, 75052117], '(00)3', +[79545240, 79545243], '3(00)', +[79652247, 79652250], '3(00)', +[83088043, 83088046], '(00)3', +[83689522, 83689525], '3(00)', +[85348958, 85348961], '(00)3', +[86513820, 86513823], '(00)3', +[87947596, 87947599], '3(00)', +[88600095, 88600098], '(00)3', +[93681183, 93681186], '(00)3', +[100316551, 100316554], '3(00)', +[100788444, 100788447], '(00)3', +[106236172, 106236175], '(00)3', +[106941327, 106941330], '3(00)', +[107287955, 107287958], '(00)3', +[107532016, 107532019], '3(00)', +[110571044, 110571047], '(00)3', +[111885253, 111885256], '3(00)', +[113239783, 113239786], '(00)3', +[120159903, 120159906], '(00)3', +[121424391, 121424394], '3(00)', +[121692931, 121692934], '3(00)', +[121934170, 121934173], '3(00)', +[122612848, 122612851], '3(00)', +[126116567, 126116570], '(00)3', +[127936513, 127936516], '(00)3', +[128710277, 128710280], '3(00)', +[129398902, 129398905], '3(00)', +[130461096, 130461099], '3(00)', +[131331947, 131331950], '3(00)', +[137334071, 137334074], '3(00)', +[137832603, 137832606], '(00)3', +[138799471, 138799474], '3(00)', +[139027791, 139027794], '(00)3', +[141617806, 141617809], '(00)3', +[144454931, 144454934], '(00)3', +[145402379, 145402382], '3(00)', +[146130245, 146130248], '3(00)', +[147059770, 147059773], '(00)3', +[147896099, 147896102], '3(00)', +[151097113, 151097116], '(00)3', +[152539438, 152539441], '(00)3', +[152863168, 152863171], '3(00)', +[153522726, 153522729], '3(00)', +[155171524, 155171527], '3(00)', +[155366607, 155366610], '(00)3', +[157260686, 157260689], '3(00)', +[157269224, 157269227], '(00)3', +[157755123, 157755126], '(00)3', +[158298484, 158298487], '3(00)', +[160369050, 160369053], '3(00)', +[162962787, 162962790], '(00)3', +[163724709, 163724712], '(00)3', +[164198113, 164198116], '3(00)', +[164689301, 164689305], '(00)40', +[164880228, 164880231], '3(00)', +[166201932, 166201935], '(00)3', +[168573836, 168573839], '(00)3', +[169750763, 169750766], '(00)3', +[170375507, 170375510], '(00)3', +[170704879, 170704882], '3(00)', +[172000992, 172000995], '3(00)', +[173289941, 173289944], '(00)3', +[173737613, 173737616], '3(00)', +[174102513, 174102516], '(00)3', +[174284990, 174284993], '(00)3', +[174500513, 174500516], '(00)3', +[175710609, 175710612], '(00)3', +[176870843, 176870846], '3(00)', +[177332732, 177332735], '3(00)', +[177902861, 177902864], '3(00)', +[179979095, 179979098], '(00)3', +[181233726, 181233729], '3(00)', +[181625435, 181625438], '(00)3', +[182105255, 182105259], '22(00)', +[182223559, 182223562], '3(00)', +[191116404, 191116407], '3(00)', +[191165599, 191165602], '3(00)', +[191297535, 191297539], '(00)22', +[192485616, 192485619], '(00)3', +[193264634, 193264638], '22(00)', +[194696968, 194696971], '(00)3', +[195876805, 195876808], '(00)3', +[195916548, 195916551], '3(00)', +[196395160, 196395163], '3(00)', +[196676303, 196676306], '(00)3', +[197889882, 197889885], '3(00)', +[198014122, 198014125], '(00)3', +[199235289, 199235292], '(00)3', +[201007375, 201007378], '(00)3', +[201030605, 201030608], '3(00)', +[201184290, 201184293], '3(00)', +[201685414, 201685418], '(00)22', +[202762875, 202762878], '3(00)', +[202860957, 202860960], '3(00)', +[203832577, 203832580], '3(00)', +[205880544, 205880547], '(00)3', +[206357111, 206357114], '(00)3', +[207159767, 207159770], '3(00)', +[207167343, 207167346], '3(00)', +[207482539, 207482543], '3(010)', +[207669540, 207669543], '3(00)', +[208053426, 208053429], '(00)3', +[208110027, 208110030], '3(00)', +[209513826, 209513829], '3(00)', +[212623522, 212623525], '(00)3', +[213841715, 213841718], '(00)3', +[214012333, 214012336], '(00)3', +[214073567, 214073570], '(00)3', +[215170600, 215170603], '3(00)', +[215881039, 215881042], '3(00)', +[216274604, 216274607], '3(00)', +[216957120, 216957123], '3(00)', +[217323208, 217323211], '(00)3', +[218799264, 218799267], '(00)3', +[218803557, 218803560], '3(00)', +[219735146, 219735149], '(00)3', +[219830062, 219830065], '3(00)', +[219897904, 219897907], '(00)3', +[221205545, 221205548], '(00)3', +[223601929, 223601932], '(00)3', +[223907076, 223907079], '3(00)', +[223970397, 223970400], '(00)3', +[224874044, 224874048], '22(00)', +[225291157, 225291160], '(00)3', +[227481734, 227481737], '(00)3', +[228006442, 228006445], '3(00)', +[228357900, 228357903], '(00)3', +[228386399, 228386402], '(00)3', +[228907446, 228907449], '(00)3', +[228984552, 228984555], '3(00)', +[229140285, 229140288], '3(00)', +[231810024, 231810027], '(00)3', +[232838062, 232838065], '3(00)', +[234389088, 234389091], '3(00)', +[235588194, 235588197], '(00)3', +[236645695, 236645698], '(00)3', +[236962876, 236962879], '3(00)', +[237516723, 237516727], '04(00)', +[240004911, 240004914], '(00)3', +[240221306, 240221309], '3(00)', +[241389213, 241389217], '(010)3', +[241549003, 241549006], '(00)3', +[241729717, 241729720], '(00)3', +[241743684, 241743687], '3(00)', +[243780200, 243780203], '3(00)', +[243801317, 243801320], '(00)3', +[244122072, 244122075], '(00)3', +[244691224, 244691227], '3(00)', +[244841577, 244841580], '(00)3', +[245813461, 245813464], '(00)3', +[246299475, 246299478], '(00)3', +[246450176, 246450179], '3(00)', +[249069349, 249069352], '(00)3', +[250076378, 250076381], '(00)3', +[252442157, 252442160], '3(00)', +[252904231, 252904234], '3(00)', +[255145220, 255145223], '(00)3', +[255285971, 255285974], '3(00)', +[256713230, 256713233], '(00)3', +[257992082, 257992085], '(00)3', +[258447955, 258447959], '22(00)', +[259298045, 259298048], '3(00)', +[262141503, 262141506], '(00)3', +[263681743, 263681746], '3(00)', +[266527881, 266527885], '(010)3', +[266617122, 266617125], '(00)3', +[266628044, 266628047], '3(00)', +[267305763, 267305766], '(00)3', +[267388404, 267388407], '3(00)', +[267441672, 267441675], '3(00)', +[267464886, 267464889], '(00)3', +[267554907, 267554910], '3(00)', +[269787480, 269787483], '(00)3', +[270881434, 270881437], '(00)3', +[270997583, 270997586], '3(00)', +[272096378, 272096381], '3(00)', +[272583009, 272583012], '(00)3', +[274190881, 274190884], '3(00)', +[274268747, 274268750], '(00)3', +[275297429, 275297432], '3(00)', +[275545476, 275545479], '3(00)', +[275898479, 275898482], '3(00)', +[275953000, 275953003], '(00)3', +[277117197, 277117201], '(00)22', +[277447310, 277447313], '3(00)', +[279059657, 279059660], '3(00)', +[279259144, 279259147], '3(00)', +[279513636, 279513639], '3(00)', +[279849069, 279849072], '3(00)', +[280291419, 280291422], '(00)3', +[281449425, 281449428], '3(00)', +[281507953, 281507956], '3(00)', +[281825600, 281825603], '(00)3', +[282547093, 282547096], '3(00)', +[283120963, 283120966], '3(00)', +[283323493, 283323496], '(00)3', +[284764535, 284764538], '3(00)', +[286172639, 286172642], '3(00)', +[286688824, 286688827], '(00)3', +[287222172, 287222175], '3(00)', +[287235534, 287235537], '3(00)', +[287304861, 287304864], '3(00)', +[287433571, 287433574], '(00)3', +[287823551, 287823554], '(00)3', +[287872422, 287872425], '3(00)', +[288766615, 288766618], '3(00)', +[290122963, 290122966], '3(00)', +[290450849, 290450853], '(00)22', +[291426141, 291426144], '3(00)', +[292810353, 292810356], '3(00)', +[293109861, 293109864], '3(00)', +[293398054, 293398057], '3(00)', +[294134426, 294134429], '3(00)', +[294216438, 294216441], '(00)3', +[295367141, 295367144], '3(00)', +[297834111, 297834114], '3(00)', +[299099969, 299099972], '3(00)', +[300746958, 300746961], '3(00)', +[301097423, 301097426], '(00)3', +[301834209, 301834212], '(00)3', +[302554791, 302554794], '(00)3', +[303497445, 303497448], '3(00)', +[304165344, 304165347], '3(00)', +[304790218, 304790222], '3(010)', +[305302352, 305302355], '(00)3', +[306785996, 306785999], '3(00)', +[307051443, 307051446], '3(00)', +[307481539, 307481542], '3(00)', +[308605569, 308605572], '3(00)', +[309237610, 309237613], '3(00)', +[310509287, 310509290], '(00)3', +[310554057, 310554060], '3(00)', +[310646345, 310646348], '3(00)', +[311274896, 311274899], '(00)3', +[311894272, 311894275], '3(00)', +[312269470, 312269473], '(00)3', +[312306601, 312306605], '(00)40', +[312683193, 312683196], '3(00)', +[314499804, 314499807], '3(00)', +[314636802, 314636805], '(00)3', +[314689897, 314689900], '3(00)', +[314721319, 314721322], '3(00)', +[316132890, 316132893], '3(00)', +[316217470, 316217474], '(010)3', +[316465705, 316465708], '3(00)', +[316542790, 316542793], '(00)3', +[320822347, 320822350], '3(00)', +[321733242, 321733245], '3(00)', +[324413970, 324413973], '(00)3', +[325950140, 325950143], '(00)3', +[326675884, 326675887], '(00)3', +[326704208, 326704211], '3(00)', +[327596247, 327596250], '3(00)', +[328123172, 328123175], '3(00)', +[328182212, 328182215], '(00)3', +[328257498, 328257501], '3(00)', +[328315836, 328315839], '(00)3', +[328800974, 328800977], '(00)3', +[328998509, 328998512], '3(00)', +[329725370, 329725373], '(00)3', +[332080601, 332080604], '(00)3', +[332221246, 332221249], '(00)3', +[332299899, 332299902], '(00)3', +[332532822, 332532825], '(00)3', +[333334544, 333334548], '(00)22', +[333881266, 333881269], '3(00)', +[334703267, 334703270], '3(00)', +[334875138, 334875141], '3(00)', +[336531451, 336531454], '3(00)', +[336825907, 336825910], '(00)3', +[336993167, 336993170], '(00)3', +[337493998, 337494001], '3(00)', +[337861034, 337861037], '3(00)', +[337899191, 337899194], '(00)3', +[337958123, 337958126], '(00)3', +[342331982, 342331985], '3(00)', +[342676068, 342676071], '3(00)', +[347063781, 347063784], '3(00)', +[347697348, 347697351], '3(00)', +[347954319, 347954322], '3(00)', +[348162775, 348162778], '3(00)', +[349210702, 349210705], '(00)3', +[349212913, 349212916], '3(00)', +[349248650, 349248653], '(00)3', +[349913500, 349913503], '3(00)', +[350891529, 350891532], '3(00)', +[351089323, 351089326], '3(00)', +[351826158, 351826161], '3(00)', +[352228580, 352228583], '(00)3', +[352376244, 352376247], '3(00)', +[352853758, 352853761], '(00)3', +[355110439, 355110442], '(00)3', +[355808090, 355808094], '(00)40', +[355941556, 355941559], '3(00)', +[356360231, 356360234], '(00)3', +[356586657, 356586660], '3(00)', +[356892926, 356892929], '(00)3', +[356908232, 356908235], '3(00)', +[357912730, 357912733], '3(00)', +[358120344, 358120347], '3(00)', +[359044096, 359044099], '(00)3', +[360819357, 360819360], '3(00)', +[361399662, 361399666], '(010)3', +[362361315, 362361318], '(00)3', +[363610112, 363610115], '(00)3', +[363964804, 363964807], '3(00)', +[364527375, 364527378], '(00)3', +[365090327, 365090330], '(00)3', +[365414539, 365414542], '3(00)', +[366738474, 366738477], '3(00)', +[368714778, 368714783], '04(010)', +[368831545, 368831548], '(00)3', +[368902387, 368902390], '(00)3', +[370109769, 370109772], '3(00)', +[370963333, 370963336], '3(00)', +[372541136, 372541140], '3(010)', +[372681562, 372681565], '(00)3', +[373009410, 373009413], '(00)3', +[373458970, 373458973], '3(00)', +[375648658, 375648661], '3(00)', +[376834728, 376834731], '3(00)', +[377119945, 377119948], '(00)3', +[377335703, 377335706], '(00)3', +[378091745, 378091748], '3(00)', +[379139522, 379139525], '3(00)', +[380279160, 380279163], '(00)3', +[380619442, 380619445], '3(00)', +[381244231, 381244234], '3(00)', +[382327446, 382327450], '(010)3', +[382357073, 382357076], '3(00)', +[383545479, 383545482], '3(00)', +[384363766, 384363769], '(00)3', +[384401786, 384401790], '22(00)', +[385198212, 385198215], '3(00)', +[385824476, 385824479], '(00)3', +[385908194, 385908197], '3(00)', +[386946806, 386946809], '3(00)', +[387592175, 387592179], '22(00)', +[388329293, 388329296], '(00)3', +[388679566, 388679569], '3(00)', +[388832142, 388832145], '3(00)', +[390087103, 390087106], '(00)3', +[390190926, 390190930], '(00)22', +[390331207, 390331210], '3(00)', +[391674495, 391674498], '3(00)', +[391937831, 391937834], '3(00)', +[391951632, 391951636], '(00)22', +[392963986, 392963989], '(00)3', +[393007921, 393007924], '3(00)', +[393373210, 393373213], '3(00)', +[393759572, 393759575], '(00)3', +[394036662, 394036665], '(00)3', +[395813866, 395813869], '(00)3', +[395956690, 395956693], '3(00)', +[396031670, 396031673], '3(00)', +[397076433, 397076436], '3(00)', +[397470601, 397470604], '3(00)', +[398289458, 398289461], '3(00)', +# +[368714778, 368714783], '04(010)', +[437953499, 437953504], '04(010)', +[526196233, 526196238], '032(00)', +[744719566, 744719571], '(010)40', +[750375857, 750375862], '032(00)', +[958241932, 958241937], '04(010)', +[983377342, 983377347], '(00)410', +[1003780080, 1003780085], '04(010)', +[1070232754, 1070232759], '(00)230', +[1209834865, 1209834870], '032(00)', +[1257209100, 1257209105], '(00)410', +[1368002233, 1368002238], '(00)230' +] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1573114afc4fbce73f2ba9d2ddc99882c00027c0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__init__.py @@ -0,0 +1,77 @@ +from .libmpf import (prec_to_dps, dps_to_prec, repr_dps, + round_down, round_up, round_floor, round_ceiling, round_nearest, + to_pickable, from_pickable, ComplexResult, + fzero, fnzero, fone, fnone, ftwo, ften, fhalf, fnan, finf, fninf, + math_float_inf, round_int, normalize, normalize1, + from_man_exp, from_int, to_man_exp, to_int, mpf_ceil, mpf_floor, + mpf_nint, mpf_frac, + from_float, from_npfloat, from_Decimal, to_float, from_rational, to_rational, to_fixed, + mpf_rand, mpf_eq, mpf_hash, mpf_cmp, mpf_lt, mpf_le, mpf_gt, mpf_ge, + mpf_pos, mpf_neg, mpf_abs, mpf_sign, mpf_add, mpf_sub, mpf_sum, + mpf_mul, mpf_mul_int, mpf_shift, mpf_frexp, + mpf_div, mpf_rdiv_int, mpf_mod, mpf_pow_int, + mpf_perturb, + to_digits_exp, to_str, str_to_man_exp, from_str, from_bstr, to_bstr, + mpf_sqrt, mpf_hypot) + +from .libmpc import (mpc_one, mpc_zero, mpc_two, mpc_half, + mpc_is_inf, mpc_is_infnan, mpc_to_str, mpc_to_complex, mpc_hash, + mpc_conjugate, mpc_is_nonzero, mpc_add, mpc_add_mpf, + mpc_sub, mpc_sub_mpf, mpc_pos, mpc_neg, mpc_shift, mpc_abs, + mpc_arg, mpc_floor, mpc_ceil, mpc_nint, mpc_frac, mpc_mul, mpc_square, + mpc_mul_mpf, mpc_mul_imag_mpf, mpc_mul_int, + mpc_div, mpc_div_mpf, mpc_reciprocal, mpc_mpf_div, + complex_int_pow, mpc_pow, mpc_pow_mpf, mpc_pow_int, + mpc_sqrt, mpc_nthroot, mpc_cbrt, mpc_exp, mpc_log, mpc_cos, mpc_sin, + mpc_tan, mpc_cos_pi, mpc_sin_pi, mpc_cosh, mpc_sinh, mpc_tanh, + mpc_atan, mpc_acos, mpc_asin, mpc_asinh, mpc_acosh, mpc_atanh, + mpc_fibonacci, mpf_expj, mpf_expjpi, mpc_expj, mpc_expjpi, + mpc_cos_sin, mpc_cos_sin_pi) + +from .libelefun import (ln2_fixed, mpf_ln2, ln10_fixed, mpf_ln10, + pi_fixed, mpf_pi, e_fixed, mpf_e, phi_fixed, mpf_phi, + degree_fixed, mpf_degree, + mpf_pow, mpf_nthroot, mpf_cbrt, log_int_fixed, agm_fixed, + mpf_log, mpf_log_hypot, mpf_exp, mpf_cos_sin, mpf_cos, mpf_sin, mpf_tan, + mpf_cos_sin_pi, mpf_cos_pi, mpf_sin_pi, mpf_cosh_sinh, + mpf_cosh, mpf_sinh, mpf_tanh, mpf_atan, mpf_atan2, mpf_asin, + mpf_acos, mpf_asinh, mpf_acosh, mpf_atanh, mpf_fibonacci) + +from .libhyper import (NoConvergence, make_hyp_summator, + mpf_erf, mpf_erfc, mpf_ei, mpc_ei, mpf_e1, mpc_e1, mpf_expint, + mpf_ci_si, mpf_ci, mpf_si, mpc_ci, mpc_si, mpf_besseljn, + mpc_besseljn, mpf_agm, mpf_agm1, mpc_agm, mpc_agm1, + mpf_ellipk, mpc_ellipk, mpf_ellipe, mpc_ellipe) + +from .gammazeta import (catalan_fixed, mpf_catalan, + khinchin_fixed, mpf_khinchin, glaisher_fixed, mpf_glaisher, + apery_fixed, mpf_apery, euler_fixed, mpf_euler, mertens_fixed, + mpf_mertens, twinprime_fixed, mpf_twinprime, + mpf_bernoulli, bernfrac, mpf_gamma_int, + mpf_factorial, mpc_factorial, mpf_gamma, mpc_gamma, + mpf_loggamma, mpc_loggamma, mpf_rgamma, mpc_rgamma, + mpf_harmonic, mpc_harmonic, mpf_psi0, mpc_psi0, + mpf_psi, mpc_psi, mpf_zeta_int, mpf_zeta, mpc_zeta, + mpf_altzeta, mpc_altzeta, mpf_zetasum, mpc_zetasum) + +from .libmpi import (mpi_str, + mpi_from_str, mpi_to_str, + mpi_eq, mpi_ne, + mpi_lt, mpi_le, mpi_gt, mpi_ge, + mpi_add, mpi_sub, mpi_delta, mpi_mid, + mpi_pos, mpi_neg, mpi_abs, mpi_mul, mpi_div, mpi_exp, + mpi_log, mpi_sqrt, mpi_pow_int, mpi_pow, mpi_cos_sin, + mpi_cos, mpi_sin, mpi_tan, mpi_cot, + mpi_atan, mpi_atan2, + mpci_pos, mpci_neg, mpci_add, mpci_sub, mpci_mul, mpci_div, mpci_pow, + mpci_abs, mpci_pow, mpci_exp, mpci_log, mpci_cos, mpci_sin, + mpi_gamma, mpci_gamma, mpi_loggamma, mpci_loggamma, + mpi_rgamma, mpci_rgamma, mpi_factorial, mpci_factorial) + +from .libintmath import (trailing, bitcount, numeral, bin_to_radix, + isqrt, isqrt_small, isqrt_fast, sqrt_fixed, sqrtrem, ifib, ifac, + list_primes, isprime, moebius, gcd, eulernum, stirling1, stirling2) + +from .backend import (gmpy, sage, BACKEND, STRICT, MPZ, MPZ_TYPE, + MPZ_ZERO, MPZ_ONE, MPZ_TWO, MPZ_THREE, MPZ_FIVE, int_types, + HASH_MODULUS, HASH_BITS) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54daef8d856646838afd50378112276c5d8f7f93 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/backend.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e85ef269703cda01c98b576e7f99219fbf0595f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/backend.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/gammazeta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/gammazeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12f373b027d25ddc49f82cd5445ce29476114cc2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/gammazeta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libelefun.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libelefun.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b81f8a192674e6b9230180ea81890da2dfb648b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libelefun.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libhyper.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libhyper.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ac19502565b946b5b58e3ba10bd4223092d5c83 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libhyper.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libintmath.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libintmath.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..906eb125959da4b8c7247708e1aa727a40b26613 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libintmath.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpc.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21a55716bb6b88028f1b16bf0657602ffa353225 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpc.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpf.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpf.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afa69d1c006a3d81df72b7f3b1d64aca0e7b4489 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpf.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpi.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..382bea451c021e7995ba4fbc3a0c77e45418ddc9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/__pycache__/libmpi.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/backend.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/backend.py new file mode 100644 index 0000000000000000000000000000000000000000..5610221290a05078f21f09df3c1a76b0e4ccdc02 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/backend.py @@ -0,0 +1,115 @@ +import os +import sys + +#----------------------------------------------------------------------------# +# Support GMPY for high-speed large integer arithmetic. # +# # +# To allow an external module to handle arithmetic, we need to make sure # +# that all high-precision variables are declared of the correct type. MPZ # +# is the constructor for the high-precision type. It defaults to Python's # +# long type but can be assinged another type, typically gmpy.mpz. # +# # +# MPZ must be used for the mantissa component of an mpf and must be used # +# for internal fixed-point operations. # +# # +# Side-effects # +# 1) "is" cannot be used to test for special values. Must use "==". # +# 2) There are bugs in GMPY prior to v1.02 so we must use v1.03 or later. # +#----------------------------------------------------------------------------# + +# So we can import it from this module +gmpy = None +sage = None +sage_utils = None + +if sys.version_info[0] < 3: + python3 = False +else: + python3 = True + +BACKEND = 'python' + +if not python3: + MPZ = long + xrange = xrange + basestring = basestring + + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") +else: + MPZ = int + xrange = range + basestring = str + + import builtins + exec_ = getattr(builtins, "exec") + +# Define constants for calculating hash on Python 3.2. +if sys.version_info >= (3, 2): + HASH_MODULUS = sys.hash_info.modulus + if sys.hash_info.width == 32: + HASH_BITS = 31 + else: + HASH_BITS = 61 +else: + HASH_MODULUS = None + HASH_BITS = None + +if 'MPMATH_NOGMPY' not in os.environ: + try: + try: + import gmpy2 as gmpy + except ImportError: + try: + import gmpy + except ImportError: + raise ImportError + if gmpy.version() >= '1.03': + BACKEND = 'gmpy' + MPZ = gmpy.mpz + except: + pass + +if ('MPMATH_NOSAGE' not in os.environ and 'SAGE_ROOT' in os.environ or + 'MPMATH_SAGE' in os.environ): + try: + import sage.all + import sage.libs.mpmath.utils as _sage_utils + sage = sage.all + sage_utils = _sage_utils + BACKEND = 'sage' + MPZ = sage.Integer + except: + pass + +if 'MPMATH_STRICT' in os.environ: + STRICT = True +else: + STRICT = False + +MPZ_TYPE = type(MPZ(0)) +MPZ_ZERO = MPZ(0) +MPZ_ONE = MPZ(1) +MPZ_TWO = MPZ(2) +MPZ_THREE = MPZ(3) +MPZ_FIVE = MPZ(5) + +try: + if BACKEND == 'python': + int_types = (int, long) + else: + int_types = (int, long, MPZ_TYPE) +except NameError: + if BACKEND == 'python': + int_types = (int,) + else: + int_types = (int, MPZ_TYPE) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/gammazeta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/gammazeta.py new file mode 100644 index 0000000000000000000000000000000000000000..3b05cc63c5f00e6c76d8383853dba06f15e46030 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/gammazeta.py @@ -0,0 +1,2167 @@ +""" +----------------------------------------------------------------------- +This module implements gamma- and zeta-related functions: + +* Bernoulli numbers +* Factorials +* The gamma function +* Polygamma functions +* Harmonic numbers +* The Riemann zeta function +* Constants related to these functions + +----------------------------------------------------------------------- +""" + +import math +import sys + +from .backend import xrange +from .backend import MPZ, MPZ_ZERO, MPZ_ONE, MPZ_THREE, gmpy + +from .libintmath import list_primes, ifac, ifac2, moebius + +from .libmpf import (\ + round_floor, round_ceiling, round_down, round_up, + round_nearest, round_fast, + lshift, sqrt_fixed, isqrt_fast, + fzero, fone, fnone, fhalf, ftwo, finf, fninf, fnan, + from_int, to_int, to_fixed, from_man_exp, from_rational, + mpf_pos, mpf_neg, mpf_abs, mpf_add, mpf_sub, + mpf_mul, mpf_mul_int, mpf_div, mpf_sqrt, mpf_pow_int, + mpf_rdiv_int, + mpf_perturb, mpf_le, mpf_lt, mpf_gt, mpf_shift, + negative_rnd, reciprocal_rnd, + bitcount, to_float, mpf_floor, mpf_sign, ComplexResult +) + +from .libelefun import (\ + constant_memo, + def_mpf_constant, + mpf_pi, pi_fixed, ln2_fixed, log_int_fixed, mpf_ln2, + mpf_exp, mpf_log, mpf_pow, mpf_cosh, + mpf_cos_sin, mpf_cosh_sinh, mpf_cos_sin_pi, mpf_cos_pi, mpf_sin_pi, + ln_sqrt2pi_fixed, mpf_ln_sqrt2pi, sqrtpi_fixed, mpf_sqrtpi, + cos_sin_fixed, exp_fixed +) + +from .libmpc import (\ + mpc_zero, mpc_one, mpc_half, mpc_two, + mpc_abs, mpc_shift, mpc_pos, mpc_neg, + mpc_add, mpc_sub, mpc_mul, mpc_div, + mpc_add_mpf, mpc_mul_mpf, mpc_div_mpf, mpc_mpf_div, + mpc_mul_int, mpc_pow_int, + mpc_log, mpc_exp, mpc_pow, + mpc_cos_pi, mpc_sin_pi, + mpc_reciprocal, mpc_square, + mpc_sub_mpf +) + + + +# Catalan's constant is computed using Lupas's rapidly convergent series +# (listed on http://mathworld.wolfram.com/CatalansConstant.html) +# oo +# ___ n-1 8n 2 3 2 +# 1 \ (-1) 2 (40n - 24n + 3) [(2n)!] (n!) +# K = --- ) ----------------------------------------- +# 64 /___ 3 2 +# n (2n-1) [(4n)!] +# n = 1 + +@constant_memo +def catalan_fixed(prec): + prec = prec + 20 + a = one = MPZ_ONE << prec + s, t, n = 0, 1, 1 + while t: + a *= 32 * n**3 * (2*n-1) + a //= (3-16*n+16*n**2)**2 + t = a * (-1)**(n-1) * (40*n**2-24*n+3) // (n**3 * (2*n-1)) + s += t + n += 1 + return s >> (20 + 6) + +# Khinchin's constant is relatively difficult to compute. Here +# we use the rational zeta series + +# oo 2*n-1 +# ___ ___ +# \ ` zeta(2*n)-1 \ ` (-1)^(k+1) +# log(K)*log(2) = ) ------------ ) ---------- +# /___. n /___. k +# n = 1 k = 1 + +# which adds half a digit per term. The essential trick for achieving +# reasonable efficiency is to recycle both the values of the zeta +# function (essentially Bernoulli numbers) and the partial terms of +# the inner sum. + +# An alternative might be to use K = 2*exp[1/log(2) X] where + +# / 1 1 [ pi*x*(1-x^2) ] +# X = | ------ log [ ------------ ]. +# / 0 x(1+x) [ sin(pi*x) ] + +# and integrate numerically. In practice, this seems to be slightly +# slower than the zeta series at high precision. + +@constant_memo +def khinchin_fixed(prec): + wp = int(prec + prec**0.5 + 15) + s = MPZ_ZERO + fac = from_int(4) + t = ONE = MPZ_ONE << wp + pi = mpf_pi(wp) + pipow = twopi2 = mpf_shift(mpf_mul(pi, pi, wp), 2) + n = 1 + while 1: + zeta2n = mpf_abs(mpf_bernoulli(2*n, wp)) + zeta2n = mpf_mul(zeta2n, pipow, wp) + zeta2n = mpf_div(zeta2n, fac, wp) + zeta2n = to_fixed(zeta2n, wp) + term = (((zeta2n - ONE) * t) // n) >> wp + if term < 100: + break + #if not n % 10: + # print n, math.log(int(abs(term))) + s += term + t += ONE//(2*n+1) - ONE//(2*n) + n += 1 + fac = mpf_mul_int(fac, (2*n)*(2*n-1), wp) + pipow = mpf_mul(pipow, twopi2, wp) + s = (s << wp) // ln2_fixed(wp) + K = mpf_exp(from_man_exp(s, -wp), wp) + K = to_fixed(K, prec) + return K + + +# Glaisher's constant is defined as A = exp(1/2 - zeta'(-1)). +# One way to compute it would be to perform direct numerical +# differentiation, but computing arbitrary Riemann zeta function +# values at high precision is expensive. We instead use the formula + +# A = exp((6 (-zeta'(2))/pi^2 + log 2 pi + gamma)/12) + +# and compute zeta'(2) from the series representation + +# oo +# ___ +# \ log k +# -zeta'(2) = ) ----- +# /___ 2 +# k +# k = 2 + +# This series converges exceptionally slowly, but can be accelerated +# using Euler-Maclaurin formula. The important insight is that the +# E-M integral can be done in closed form and that the high order +# are given by + +# n / \ +# d | log x | a + b log x +# --- | ----- | = ----------- +# n | 2 | 2 + n +# dx \ x / x + +# where a and b are integers given by a simple recurrence. Note +# that just one logarithm is needed. However, lots of integer +# logarithms are required for the initial summation. + +# This algorithm could possibly be turned into a faster algorithm +# for general evaluation of zeta(s) or zeta'(s); this should be +# looked into. + +@constant_memo +def glaisher_fixed(prec): + wp = prec + 30 + # Number of direct terms to sum before applying the Euler-Maclaurin + # formula to the tail. TODO: choose more intelligently + N = int(0.33*prec + 5) + ONE = MPZ_ONE << wp + # Euler-Maclaurin, step 1: sum log(k)/k**2 for k from 2 to N-1 + s = MPZ_ZERO + for k in range(2, N): + #print k, N + s += log_int_fixed(k, wp) // k**2 + logN = log_int_fixed(N, wp) + #logN = to_fixed(mpf_log(from_int(N), wp+20), wp) + # E-M step 2: integral of log(x)/x**2 from N to inf + s += (ONE + logN) // N + # E-M step 3: endpoint correction term f(N)/2 + s += logN // (N**2 * 2) + # E-M step 4: the series of derivatives + pN = N**3 + a = 1 + b = -2 + j = 3 + fac = from_int(2) + k = 1 + while 1: + # D(2*k-1) * B(2*k) / fac(2*k) [D(n) = nth derivative] + D = ((a << wp) + b*logN) // pN + D = from_man_exp(D, -wp) + B = mpf_bernoulli(2*k, wp) + term = mpf_mul(B, D, wp) + term = mpf_div(term, fac, wp) + term = to_fixed(term, wp) + if abs(term) < 100: + break + #if not k % 10: + # print k, math.log(int(abs(term)), 10) + s -= term + # Advance derivative twice + a, b, pN, j = b-a*j, -j*b, pN*N, j+1 + a, b, pN, j = b-a*j, -j*b, pN*N, j+1 + k += 1 + fac = mpf_mul_int(fac, (2*k)*(2*k-1), wp) + # A = exp((6*s/pi**2 + log(2*pi) + euler)/12) + pi = pi_fixed(wp) + s *= 6 + s = (s << wp) // (pi**2 >> wp) + s += euler_fixed(wp) + s += to_fixed(mpf_log(from_man_exp(2*pi, -wp), wp), wp) + s //= 12 + A = mpf_exp(from_man_exp(s, -wp), wp) + return to_fixed(A, prec) + +# Apery's constant can be computed using the very rapidly convergent +# series +# oo +# ___ 2 10 +# \ n 205 n + 250 n + 77 (n!) +# zeta(3) = ) (-1) ------------------- ---------- +# /___ 64 5 +# n = 0 ((2n+1)!) + +@constant_memo +def apery_fixed(prec): + prec += 20 + d = MPZ_ONE << prec + term = MPZ(77) << prec + n = 1 + s = MPZ_ZERO + while term: + s += term + d *= (n**10) + d //= (((2*n+1)**5) * (2*n)**5) + term = (-1)**n * (205*(n**2) + 250*n + 77) * d + n += 1 + return s >> (20 + 6) + +""" +Euler's constant (gamma) is computed using the Brent-McMillan formula, +gamma ~= I(n)/J(n) - log(n), where + + I(n) = sum_{k=0,1,2,...} (n**k / k!)**2 * H(k) + J(n) = sum_{k=0,1,2,...} (n**k / k!)**2 + H(k) = 1 + 1/2 + 1/3 + ... + 1/k + +The error is bounded by O(exp(-4n)). Choosing n to be a power +of two, 2**p, the logarithm becomes particularly easy to calculate.[1] + +We use the formulation of Algorithm 3.9 in [2] to make the summation +more efficient. + +Reference: +[1] Xavier Gourdon & Pascal Sebah, The Euler constant: gamma +http://numbers.computation.free.fr/Constants/Gamma/gamma.pdf + +[2] [BorweinBailey]_ +""" + +@constant_memo +def euler_fixed(prec): + extra = 30 + prec += extra + # choose p such that exp(-4*(2**p)) < 2**-n + p = int(math.log((prec/4) * math.log(2), 2)) + 1 + n = 2**p + A = U = -p*ln2_fixed(prec) + B = V = MPZ_ONE << prec + k = 1 + while 1: + B = B*n**2//k**2 + A = (A*n**2//k + B)//k + U += A + V += B + if max(abs(A), abs(B)) < 100: + break + k += 1 + return (U<<(prec-extra))//V + +# Use zeta accelerated formulas for the Mertens and twin +# prime constants; see +# http://mathworld.wolfram.com/MertensConstant.html +# http://mathworld.wolfram.com/TwinPrimesConstant.html + +@constant_memo +def mertens_fixed(prec): + wp = prec + 20 + m = 2 + s = mpf_euler(wp) + while 1: + t = mpf_zeta_int(m, wp) + if t == fone: + break + t = mpf_log(t, wp) + t = mpf_mul_int(t, moebius(m), wp) + t = mpf_div(t, from_int(m), wp) + s = mpf_add(s, t) + m += 1 + return to_fixed(s, prec) + +@constant_memo +def twinprime_fixed(prec): + def I(n): + return sum(moebius(d)<<(n//d) for d in xrange(1,n+1) if not n%d)//n + wp = 2*prec + 30 + res = fone + primes = [from_rational(1,p,wp) for p in [2,3,5,7]] + ppowers = [mpf_mul(p,p,wp) for p in primes] + n = 2 + while 1: + a = mpf_zeta_int(n, wp) + for i in range(4): + a = mpf_mul(a, mpf_sub(fone, ppowers[i]), wp) + ppowers[i] = mpf_mul(ppowers[i], primes[i], wp) + a = mpf_pow_int(a, -I(n), wp) + if mpf_pos(a, prec+10, 'n') == fone: + break + #from libmpf import to_str + #print n, to_str(mpf_sub(fone, a), 6) + res = mpf_mul(res, a, wp) + n += 1 + res = mpf_mul(res, from_int(3*15*35), wp) + res = mpf_div(res, from_int(4*16*36), wp) + return to_fixed(res, prec) + + +mpf_euler = def_mpf_constant(euler_fixed) +mpf_apery = def_mpf_constant(apery_fixed) +mpf_khinchin = def_mpf_constant(khinchin_fixed) +mpf_glaisher = def_mpf_constant(glaisher_fixed) +mpf_catalan = def_mpf_constant(catalan_fixed) +mpf_mertens = def_mpf_constant(mertens_fixed) +mpf_twinprime = def_mpf_constant(twinprime_fixed) + + +#-----------------------------------------------------------------------# +# # +# Bernoulli numbers # +# # +#-----------------------------------------------------------------------# + +MAX_BERNOULLI_CACHE = 3000 + + +r""" +Small Bernoulli numbers and factorials are used in numerous summations, +so it is critical for speed that sequential computation is fast and that +values are cached up to a fairly high threshold. + +On the other hand, we also want to support fast computation of isolated +large numbers. Currently, no such acceleration is provided for integer +factorials (though it is for large floating-point factorials, which are +computed via gamma if the precision is low enough). + +For sequential computation of Bernoulli numbers, we use Ramanujan's formula + + / n + 3 \ + B = (A(n) - S(n)) / | | + n \ n / + +where A(n) = (n+3)/3 when n = 0 or 2 (mod 6), A(n) = -(n+3)/6 +when n = 4 (mod 6), and + + [n/6] + ___ + \ / n + 3 \ + S(n) = ) | | * B + /___ \ n - 6*k / n-6*k + k = 1 + +For isolated large Bernoulli numbers, we use the Riemann zeta function +to calculate a numerical value for B_n. The von Staudt-Clausen theorem +can then be used to optionally find the exact value of the +numerator and denominator. +""" + +bernoulli_cache = {} +f3 = from_int(3) +f6 = from_int(6) + +def bernoulli_size(n): + """Accurately estimate the size of B_n (even n > 2 only)""" + lgn = math.log(n,2) + return int(2.326 + 0.5*lgn + n*(lgn - 4.094)) + +BERNOULLI_PREC_CUTOFF = bernoulli_size(MAX_BERNOULLI_CACHE) + +def mpf_bernoulli(n, prec, rnd=None): + """Computation of Bernoulli numbers (numerically)""" + if n < 2: + if n < 0: + raise ValueError("Bernoulli numbers only defined for n >= 0") + if n == 0: + return fone + if n == 1: + return mpf_neg(fhalf) + # For odd n > 1, the Bernoulli numbers are zero + if n & 1: + return fzero + # If precision is extremely high, we can save time by computing + # the Bernoulli number at a lower precision that is sufficient to + # obtain the exact fraction, round to the exact fraction, and + # convert the fraction back to an mpf value at the original precision + if prec > BERNOULLI_PREC_CUTOFF and prec > bernoulli_size(n)*1.1 + 1000: + p, q = bernfrac(n) + return from_rational(p, q, prec, rnd or round_floor) + if n > MAX_BERNOULLI_CACHE: + return mpf_bernoulli_huge(n, prec, rnd) + wp = prec + 30 + # Reuse nearby precisions + wp += 32 - (prec & 31) + cached = bernoulli_cache.get(wp) + if cached: + numbers, state = cached + if n in numbers: + if not rnd: + return numbers[n] + return mpf_pos(numbers[n], prec, rnd) + m, bin, bin1 = state + if n - m > 10: + return mpf_bernoulli_huge(n, prec, rnd) + else: + if n > 10: + return mpf_bernoulli_huge(n, prec, rnd) + numbers = {0:fone} + m, bin, bin1 = state = [2, MPZ(10), MPZ_ONE] + bernoulli_cache[wp] = (numbers, state) + while m <= n: + #print m + case = m % 6 + # Accurately estimate size of B_m so we can use + # fixed point math without using too much precision + szbm = bernoulli_size(m) + s = 0 + sexp = max(0, szbm) - wp + if m < 6: + a = MPZ_ZERO + else: + a = bin1 + for j in xrange(1, m//6+1): + usign, uman, uexp, ubc = u = numbers[m-6*j] + if usign: + uman = -uman + s += lshift(a*uman, uexp-sexp) + # Update inner binomial coefficient + j6 = 6*j + a *= ((m-5-j6)*(m-4-j6)*(m-3-j6)*(m-2-j6)*(m-1-j6)*(m-j6)) + a //= ((4+j6)*(5+j6)*(6+j6)*(7+j6)*(8+j6)*(9+j6)) + if case == 0: b = mpf_rdiv_int(m+3, f3, wp) + if case == 2: b = mpf_rdiv_int(m+3, f3, wp) + if case == 4: b = mpf_rdiv_int(-m-3, f6, wp) + s = from_man_exp(s, sexp, wp) + b = mpf_div(mpf_sub(b, s, wp), from_int(bin), wp) + numbers[m] = b + m += 2 + # Update outer binomial coefficient + bin = bin * ((m+2)*(m+3)) // (m*(m-1)) + if m > 6: + bin1 = bin1 * ((2+m)*(3+m)) // ((m-7)*(m-6)) + state[:] = [m, bin, bin1] + return numbers[n] + +def mpf_bernoulli_huge(n, prec, rnd=None): + wp = prec + 10 + piprec = wp + int(math.log(n,2)) + v = mpf_gamma_int(n+1, wp) + v = mpf_mul(v, mpf_zeta_int(n, wp), wp) + v = mpf_mul(v, mpf_pow_int(mpf_pi(piprec), -n, wp)) + v = mpf_shift(v, 1-n) + if not n & 3: + v = mpf_neg(v) + return mpf_pos(v, prec, rnd or round_fast) + +def bernfrac(n): + r""" + Returns a tuple of integers `(p, q)` such that `p/q = B_n` exactly, + where `B_n` denotes the `n`-th Bernoulli number. The fraction is + always reduced to lowest terms. Note that for `n > 1` and `n` odd, + `B_n = 0`, and `(0, 1)` is returned. + + **Examples** + + The first few Bernoulli numbers are exactly:: + + >>> from mpmath import * + >>> for n in range(15): + ... p, q = bernfrac(n) + ... print("%s %s/%s" % (n, p, q)) + ... + 0 1/1 + 1 -1/2 + 2 1/6 + 3 0/1 + 4 -1/30 + 5 0/1 + 6 1/42 + 7 0/1 + 8 -1/30 + 9 0/1 + 10 5/66 + 11 0/1 + 12 -691/2730 + 13 0/1 + 14 7/6 + + This function works for arbitrarily large `n`:: + + >>> p, q = bernfrac(10**4) + >>> print(q) + 2338224387510 + >>> print(len(str(p))) + 27692 + >>> mp.dps = 15 + >>> print(mpf(p) / q) + -9.04942396360948e+27677 + >>> print(bernoulli(10**4)) + -9.04942396360948e+27677 + + .. note :: + + :func:`~mpmath.bernoulli` computes a floating-point approximation + directly, without computing the exact fraction first. + This is much faster for large `n`. + + **Algorithm** + + :func:`~mpmath.bernfrac` works by computing the value of `B_n` numerically + and then using the von Staudt-Clausen theorem [1] to reconstruct + the exact fraction. For large `n`, this is significantly faster than + computing `B_1, B_2, \ldots, B_2` recursively with exact arithmetic. + The implementation has been tested for `n = 10^m` up to `m = 6`. + + In practice, :func:`~mpmath.bernfrac` appears to be about three times + slower than the specialized program calcbn.exe [2] + + **References** + + 1. MathWorld, von Staudt-Clausen Theorem: + http://mathworld.wolfram.com/vonStaudt-ClausenTheorem.html + + 2. The Bernoulli Number Page: + http://www.bernoulli.org/ + + """ + n = int(n) + if n < 3: + return [(1, 1), (-1, 2), (1, 6)][n] + if n & 1: + return (0, 1) + q = 1 + for k in list_primes(n+1): + if not (n % (k-1)): + q *= k + prec = bernoulli_size(n) + int(math.log(q,2)) + 20 + b = mpf_bernoulli(n, prec) + p = mpf_mul(b, from_int(q)) + pint = to_int(p, round_nearest) + return (pint, q) + + +#-----------------------------------------------------------------------# +# # +# Polygamma functions # +# # +#-----------------------------------------------------------------------# + +r""" +For all polygamma (psi) functions, we use the Euler-Maclaurin summation +formula. It looks slightly different in the m = 0 and m > 0 cases. + +For m = 0, we have + oo + ___ B + (0) 1 \ 2 k -2 k + psi (z) ~ log z + --- - ) ------ z + 2 z /___ (2 k)! + k = 1 + +Experiment shows that the minimum term of the asymptotic series +reaches 2^(-p) when Re(z) > 0.11*p. So we simply use the recurrence +for psi (equivalent, in fact, to summing to the first few terms +directly before applying E-M) to obtain z large enough. + +Since, very crudely, log z ~= 1 for Re(z) > 1, we can use +fixed-point arithmetic (if z is extremely large, log(z) itself +is a sufficient approximation, so we can stop there already). + +For Re(z) << 0, we could use recurrence, but this is of course +inefficient for large negative z, so there we use the +reflection formula instead. + +For m > 0, we have + + N - 1 + ___ + ~~~(m) [ \ 1 ] 1 1 + psi (z) ~ [ ) -------- ] + ---------- + -------- + + [ /___ m+1 ] m+1 m + k = 1 (z+k) ] 2 (z+N) m (z+N) + + oo + ___ B + \ 2 k (m+1) (m+2) ... (m+2k-1) + + ) ------ ------------------------ + /___ (2 k)! m + 2 k + k = 1 (z+N) + +where ~~~ denotes the function rescaled by 1/((-1)^(m+1) m!). + +Here again N is chosen to make z+N large enough for the minimum +term in the last series to become smaller than eps. + +TODO: the current estimation of N for m > 0 is *very suboptimal*. + +TODO: implement the reflection formula for m > 0, Re(z) << 0. +It is generally a combination of multiple cotangents. Need to +figure out a reasonably simple way to generate these formulas +on the fly. + +TODO: maybe use exact algorithms to compute psi for integral +and certain rational arguments, as this can be much more +efficient. (On the other hand, the availability of these +special values provides a convenient way to test the general +algorithm.) +""" + +# Harmonic numbers are just shifted digamma functions +# We should calculate these exactly when x is an integer +# and when doing so is faster. + +def mpf_harmonic(x, prec, rnd): + if x in (fzero, fnan, finf): + return x + a = mpf_psi0(mpf_add(fone, x, prec+5), prec) + return mpf_add(a, mpf_euler(prec+5, rnd), prec, rnd) + +def mpc_harmonic(z, prec, rnd): + if z[1] == fzero: + return (mpf_harmonic(z[0], prec, rnd), fzero) + a = mpc_psi0(mpc_add_mpf(z, fone, prec+5), prec) + return mpc_add_mpf(a, mpf_euler(prec+5, rnd), prec, rnd) + +def mpf_psi0(x, prec, rnd=round_fast): + """ + Computation of the digamma function (psi function of order 0) + of a real argument. + """ + sign, man, exp, bc = x + wp = prec + 10 + if not man: + if x == finf: return x + if x == fninf or x == fnan: return fnan + if x == fzero or (exp >= 0 and sign): + raise ValueError("polygamma pole") + # Near 0 -- fixed-point arithmetic becomes bad + if exp+bc < -5: + v = mpf_psi0(mpf_add(x, fone, prec, rnd), prec, rnd) + return mpf_sub(v, mpf_div(fone, x, wp, rnd), prec, rnd) + # Reflection formula + if sign and exp+bc > 3: + c, s = mpf_cos_sin_pi(x, wp) + q = mpf_mul(mpf_div(c, s, wp), mpf_pi(wp), wp) + p = mpf_psi0(mpf_sub(fone, x, wp), wp) + return mpf_sub(p, q, prec, rnd) + # The logarithmic term is accurate enough + if (not sign) and bc + exp > wp: + return mpf_log(mpf_sub(x, fone, wp), prec, rnd) + # Initial recurrence to obtain a large enough x + m = to_int(x) + n = int(0.11*wp) + 2 + s = MPZ_ZERO + x = to_fixed(x, wp) + one = MPZ_ONE << wp + if m < n: + for k in xrange(m, n): + s -= (one << wp) // x + x += one + x -= one + # Logarithmic term + s += to_fixed(mpf_log(from_man_exp(x, -wp, wp), wp), wp) + # Endpoint term in Euler-Maclaurin expansion + s += (one << wp) // (2*x) + # Euler-Maclaurin remainder sum + x2 = (x*x) >> wp + t = one + prev = 0 + k = 1 + while 1: + t = (t*x2) >> wp + bsign, bman, bexp, bbc = mpf_bernoulli(2*k, wp) + offset = (bexp + 2*wp) + if offset >= 0: term = (bman << offset) // (t*(2*k)) + else: term = (bman >> (-offset)) // (t*(2*k)) + if k & 1: s -= term + else: s += term + if k > 2 and term >= prev: + break + prev = term + k += 1 + return from_man_exp(s, -wp, wp, rnd) + +def mpc_psi0(z, prec, rnd=round_fast): + """ + Computation of the digamma function (psi function of order 0) + of a complex argument. + """ + re, im = z + # Fall back to the real case + if im == fzero: + return (mpf_psi0(re, prec, rnd), fzero) + wp = prec + 20 + sign, man, exp, bc = re + # Reflection formula + if sign and exp+bc > 3: + c = mpc_cos_pi(z, wp) + s = mpc_sin_pi(z, wp) + q = mpc_mul_mpf(mpc_div(c, s, wp), mpf_pi(wp), wp) + p = mpc_psi0(mpc_sub(mpc_one, z, wp), wp) + return mpc_sub(p, q, prec, rnd) + # Just the logarithmic term + if (not sign) and bc + exp > wp: + return mpc_log(mpc_sub(z, mpc_one, wp), prec, rnd) + # Initial recurrence to obtain a large enough z + w = to_int(re) + n = int(0.11*wp) + 2 + s = mpc_zero + if w < n: + for k in xrange(w, n): + s = mpc_sub(s, mpc_reciprocal(z, wp), wp) + z = mpc_add_mpf(z, fone, wp) + z = mpc_sub(z, mpc_one, wp) + # Logarithmic and endpoint term + s = mpc_add(s, mpc_log(z, wp), wp) + s = mpc_add(s, mpc_div(mpc_half, z, wp), wp) + # Euler-Maclaurin remainder sum + z2 = mpc_square(z, wp) + t = mpc_one + prev = mpc_zero + szprev = fzero + k = 1 + eps = mpf_shift(fone, -wp+2) + while 1: + t = mpc_mul(t, z2, wp) + bern = mpf_bernoulli(2*k, wp) + term = mpc_mpf_div(bern, mpc_mul_int(t, 2*k, wp), wp) + s = mpc_sub(s, term, wp) + szterm = mpc_abs(term, 10) + if k > 2 and (mpf_le(szterm, eps) or mpf_le(szprev, szterm)): + break + prev = term + szprev = szterm + k += 1 + return s + +# Currently unoptimized +def mpf_psi(m, x, prec, rnd=round_fast): + """ + Computation of the polygamma function of arbitrary integer order + m >= 0, for a real argument x. + """ + if m == 0: + return mpf_psi0(x, prec, rnd=round_fast) + return mpc_psi(m, (x, fzero), prec, rnd)[0] + +def mpc_psi(m, z, prec, rnd=round_fast): + """ + Computation of the polygamma function of arbitrary integer order + m >= 0, for a complex argument z. + """ + if m == 0: + return mpc_psi0(z, prec, rnd) + re, im = z + wp = prec + 20 + sign, man, exp, bc = re + if not im[1]: + if im in (finf, fninf, fnan): + return (fnan, fnan) + if not man: + if re == finf and im == fzero: + return (fzero, fzero) + if re == fnan: + return (fnan, fnan) + # Recurrence + w = to_int(re) + n = int(0.4*wp + 4*m) + s = mpc_zero + if w < n: + for k in xrange(w, n): + t = mpc_pow_int(z, -m-1, wp) + s = mpc_add(s, t, wp) + z = mpc_add_mpf(z, fone, wp) + zm = mpc_pow_int(z, -m, wp) + z2 = mpc_pow_int(z, -2, wp) + # 1/m*(z+N)^m + integral_term = mpc_div_mpf(zm, from_int(m), wp) + s = mpc_add(s, integral_term, wp) + # 1/2*(z+N)^(-(m+1)) + s = mpc_add(s, mpc_mul_mpf(mpc_div(zm, z, wp), fhalf, wp), wp) + a = m + 1 + b = 2 + k = 1 + # Important: we want to sum up to the *relative* error, + # not the absolute error, because psi^(m)(z) might be tiny + magn = mpc_abs(s, 10) + magn = magn[2]+magn[3] + eps = mpf_shift(fone, magn-wp+2) + while 1: + zm = mpc_mul(zm, z2, wp) + bern = mpf_bernoulli(2*k, wp) + scal = mpf_mul_int(bern, a, wp) + scal = mpf_div(scal, from_int(b), wp) + term = mpc_mul_mpf(zm, scal, wp) + s = mpc_add(s, term, wp) + szterm = mpc_abs(term, 10) + if k > 2 and mpf_le(szterm, eps): + break + #print k, to_str(szterm, 10), to_str(eps, 10) + a *= (m+2*k)*(m+2*k+1) + b *= (2*k+1)*(2*k+2) + k += 1 + # Scale and sign factor + v = mpc_mul_mpf(s, mpf_gamma(from_int(m+1), wp), prec, rnd) + if not (m & 1): + v = mpf_neg(v[0]), mpf_neg(v[1]) + return v + + +#-----------------------------------------------------------------------# +# # +# Riemann zeta function # +# # +#-----------------------------------------------------------------------# + +r""" +We use zeta(s) = eta(s) / (1 - 2**(1-s)) and Borwein's approximation + + n-1 + ___ k + -1 \ (-1) (d_k - d_n) + eta(s) ~= ---- ) ------------------ + d_n /___ s + k = 0 (k + 1) +where + k + ___ i + \ (n + i - 1)! 4 + d_k = n ) ---------------. + /___ (n - i)! (2i)! + i = 0 + +If s = a + b*I, the absolute error for eta(s) is bounded by + + 3 (1 + 2|b|) + ------------ * exp(|b| pi/2) + n + (3+sqrt(8)) + +Disregarding the linear term, we have approximately, + + log(err) ~= log(exp(1.58*|b|)) - log(5.8**n) + log(err) ~= 1.58*|b| - log(5.8)*n + log(err) ~= 1.58*|b| - 1.76*n + log2(err) ~= 2.28*|b| - 2.54*n + +So for p bits, we should choose n > (p + 2.28*|b|) / 2.54. + +References: +----------- + +Peter Borwein, "An Efficient Algorithm for the Riemann Zeta Function" +http://www.cecm.sfu.ca/personal/pborwein/PAPERS/P117.ps + +http://en.wikipedia.org/wiki/Dirichlet_eta_function +""" + +borwein_cache = {} + +def borwein_coefficients(n): + if n in borwein_cache: + return borwein_cache[n] + ds = [MPZ_ZERO] * (n+1) + d = MPZ_ONE + s = ds[0] = MPZ_ONE + for i in range(1, n+1): + d = d * 4 * (n+i-1) * (n-i+1) + d //= ((2*i) * ((2*i)-1)) + s += d + ds[i] = s + borwein_cache[n] = ds + return ds + +ZETA_INT_CACHE_MAX_PREC = 1000 +zeta_int_cache = {} + +def mpf_zeta_int(s, prec, rnd=round_fast): + """ + Optimized computation of zeta(s) for an integer s. + """ + wp = prec + 20 + s = int(s) + if s in zeta_int_cache and zeta_int_cache[s][0] >= wp: + return mpf_pos(zeta_int_cache[s][1], prec, rnd) + if s < 2: + if s == 1: + raise ValueError("zeta(1) pole") + if not s: + return mpf_neg(fhalf) + return mpf_div(mpf_bernoulli(-s+1, wp), from_int(s-1), prec, rnd) + # 2^-s term vanishes? + if s >= wp: + return mpf_perturb(fone, 0, prec, rnd) + # 5^-s term vanishes? + elif s >= wp*0.431: + t = one = 1 << wp + t += 1 << (wp - s) + t += one // (MPZ_THREE ** s) + t += 1 << max(0, wp - s*2) + return from_man_exp(t, -wp, prec, rnd) + else: + # Fast enough to sum directly? + # Even better, we use the Euler product (idea stolen from pari) + m = (float(wp)/(s-1) + 1) + if m < 30: + needed_terms = int(2.0**m + 1) + if needed_terms < int(wp/2.54 + 5) / 10: + t = fone + for k in list_primes(needed_terms): + #print k, needed_terms + powprec = int(wp - s*math.log(k,2)) + if powprec < 2: + break + a = mpf_sub(fone, mpf_pow_int(from_int(k), -s, powprec), wp) + t = mpf_mul(t, a, wp) + return mpf_div(fone, t, wp) + # Use Borwein's algorithm + n = int(wp/2.54 + 5) + d = borwein_coefficients(n) + t = MPZ_ZERO + s = MPZ(s) + for k in xrange(n): + t += (((-1)**k * (d[k] - d[n])) << wp) // (k+1)**s + t = (t << wp) // (-d[n]) + t = (t << wp) // ((1 << wp) - (1 << (wp+1-s))) + if (s in zeta_int_cache and zeta_int_cache[s][0] < wp) or (s not in zeta_int_cache): + zeta_int_cache[s] = (wp, from_man_exp(t, -wp-wp)) + return from_man_exp(t, -wp-wp, prec, rnd) + +def mpf_zeta(s, prec, rnd=round_fast, alt=0): + sign, man, exp, bc = s + if not man: + if s == fzero: + if alt: + return fhalf + else: + return mpf_neg(fhalf) + if s == finf: + return fone + return fnan + wp = prec + 20 + # First term vanishes? + if (not sign) and (exp + bc > (math.log(wp,2) + 2)): + return mpf_perturb(fone, alt, prec, rnd) + # Optimize for integer arguments + elif exp >= 0: + if alt: + if s == fone: + return mpf_ln2(prec, rnd) + z = mpf_zeta_int(to_int(s), wp, negative_rnd[rnd]) + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_mul(z, q, prec, rnd) + else: + return mpf_zeta_int(to_int(s), prec, rnd) + # Negative: use the reflection formula + # Borwein only proves the accuracy bound for x >= 1/2. However, based on + # tests, the accuracy without reflection is quite good even some distance + # to the left of 1/2. XXX: verify this. + if sign: + # XXX: could use the separate refl. formula for Dirichlet eta + if alt: + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_mul(mpf_zeta(s, wp), q, prec, rnd) + # XXX: -1 should be done exactly + y = mpf_sub(fone, s, 10*wp) + a = mpf_gamma(y, wp) + b = mpf_zeta(y, wp) + c = mpf_sin_pi(mpf_shift(s, -1), wp) + wp2 = wp + max(0,exp+bc) + pi = mpf_pi(wp+wp2) + d = mpf_div(mpf_pow(mpf_shift(pi, 1), s, wp2), pi, wp2) + return mpf_mul(a,mpf_mul(b,mpf_mul(c,d,wp),wp),prec,rnd) + + # Near pole + r = mpf_sub(fone, s, wp) + asign, aman, aexp, abc = mpf_abs(r) + pole_dist = -2*(aexp+abc) + if pole_dist > wp: + if alt: + return mpf_ln2(prec, rnd) + else: + q = mpf_neg(mpf_div(fone, r, wp)) + return mpf_add(q, mpf_euler(wp), prec, rnd) + else: + wp += max(0, pole_dist) + + t = MPZ_ZERO + #wp += 16 - (prec & 15) + # Use Borwein's algorithm + n = int(wp/2.54 + 5) + d = borwein_coefficients(n) + t = MPZ_ZERO + sf = to_fixed(s, wp) + ln2 = ln2_fixed(wp) + for k in xrange(n): + u = (-sf*log_int_fixed(k+1, wp, ln2)) >> wp + #esign, eman, eexp, ebc = mpf_exp(u, wp) + #offset = eexp + wp + #if offset >= 0: + # w = ((d[k] - d[n]) * eman) << offset + #else: + # w = ((d[k] - d[n]) * eman) >> (-offset) + eman = exp_fixed(u, wp, ln2) + w = (d[k] - d[n]) * eman + if k & 1: + t -= w + else: + t += w + t = t // (-d[n]) + t = from_man_exp(t, -wp, wp) + if alt: + return mpf_pos(t, prec, rnd) + else: + q = mpf_sub(fone, mpf_pow(ftwo, mpf_sub(fone, s, wp), wp), wp) + return mpf_div(t, q, prec, rnd) + +def mpc_zeta(s, prec, rnd=round_fast, alt=0, force=False): + re, im = s + if im == fzero: + return mpf_zeta(re, prec, rnd, alt), fzero + + # slow for large s + if (not force) and mpf_gt(mpc_abs(s, 10), from_int(prec)): + raise NotImplementedError + + wp = prec + 20 + + # Near pole + r = mpc_sub(mpc_one, s, wp) + asign, aman, aexp, abc = mpc_abs(r, 10) + pole_dist = -2*(aexp+abc) + if pole_dist > wp: + if alt: + q = mpf_ln2(wp) + y = mpf_mul(q, mpf_euler(wp), wp) + g = mpf_shift(mpf_mul(q, q, wp), -1) + g = mpf_sub(y, g) + z = mpc_mul_mpf(r, mpf_neg(g), wp) + z = mpc_add_mpf(z, q, wp) + return mpc_pos(z, prec, rnd) + else: + q = mpc_neg(mpc_div(mpc_one, r, wp)) + q = mpc_add_mpf(q, mpf_euler(wp), wp) + return mpc_pos(q, prec, rnd) + else: + wp += max(0, pole_dist) + + # Reflection formula. To be rigorous, we should reflect to the left of + # re = 1/2 (see comments for mpf_zeta), but this leads to unnecessary + # slowdown for interesting values of s + if mpf_lt(re, fzero): + # XXX: could use the separate refl. formula for Dirichlet eta + if alt: + q = mpc_sub(mpc_one, mpc_pow(mpc_two, mpc_sub(mpc_one, s, wp), + wp), wp) + return mpc_mul(mpc_zeta(s, wp), q, prec, rnd) + # XXX: -1 should be done exactly + y = mpc_sub(mpc_one, s, 10*wp) + a = mpc_gamma(y, wp) + b = mpc_zeta(y, wp) + c = mpc_sin_pi(mpc_shift(s, -1), wp) + rsign, rman, rexp, rbc = re + isign, iman, iexp, ibc = im + mag = max(rexp+rbc, iexp+ibc) + wp2 = wp + max(0, mag) + pi = mpf_pi(wp+wp2) + pi2 = (mpf_shift(pi, 1), fzero) + d = mpc_div_mpf(mpc_pow(pi2, s, wp2), pi, wp2) + return mpc_mul(a,mpc_mul(b,mpc_mul(c,d,wp),wp),prec,rnd) + n = int(wp/2.54 + 5) + n += int(0.9*abs(to_int(im))) + d = borwein_coefficients(n) + ref = to_fixed(re, wp) + imf = to_fixed(im, wp) + tre = MPZ_ZERO + tim = MPZ_ZERO + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + critical_line = re == fhalf + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + wp2 = wp+wp + for k in xrange(n): + log = log_int_fixed(k+1, wp, ln2) + # A square root is much cheaper than an exp + if critical_line: + w = one_2wp // isqrt_fast((k+1) << wp2) + else: + w = exp_fixed((-ref*log) >> wp, wp) + if k & 1: + w *= (d[n] - d[k]) + else: + w *= (d[k] - d[n]) + wre, wim = cos_sin_fixed((-imf*log)>>wp, wp, pi2) + tre += (w * wre) >> wp + tim += (w * wim) >> wp + tre //= (-d[n]) + tim //= (-d[n]) + tre = from_man_exp(tre, -wp, wp) + tim = from_man_exp(tim, -wp, wp) + if alt: + return mpc_pos((tre, tim), prec, rnd) + else: + q = mpc_sub(mpc_one, mpc_pow(mpc_two, r, wp), wp) + return mpc_div((tre, tim), q, prec, rnd) + +def mpf_altzeta(s, prec, rnd=round_fast): + return mpf_zeta(s, prec, rnd, 1) + +def mpc_altzeta(s, prec, rnd=round_fast): + return mpc_zeta(s, prec, rnd, 1) + +# Not optimized currently +mpf_zetasum = None + + +def pow_fixed(x, n, wp): + if n == 1: + return x + y = MPZ_ONE << wp + while n: + if n & 1: + y = (y*x) >> wp + n -= 1 + x = (x*x) >> wp + n //= 2 + return y + +# TODO: optimize / cleanup interface / unify with list_primes +sieve_cache = [] +primes_cache = [] +mult_cache = [] + +def primesieve(n): + global sieve_cache, primes_cache, mult_cache + if n < len(sieve_cache): + sieve = sieve_cache#[:n+1] + primes = primes_cache[:primes_cache.index(max(sieve))+1] + mult = mult_cache#[:n+1] + return sieve, primes, mult + sieve = [0] * (n+1) + mult = [0] * (n+1) + primes = list_primes(n) + for p in primes: + #sieve[p::p] = p + for k in xrange(p,n+1,p): + sieve[k] = p + for i, p in enumerate(sieve): + if i >= 2: + m = 1 + n = i // p + while not n % p: + n //= p + m += 1 + mult[i] = m + sieve_cache = sieve + primes_cache = primes + mult_cache = mult + return sieve, primes, mult + +def zetasum_sieved(critical_line, sre, sim, a, n, wp): + if a < 1: + raise ValueError("a cannot be less than 1") + sieve, primes, mult = primesieve(a+n) + basic_powers = {} + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + wp2 = wp+wp + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + for p in primes: + if p*2 > a+n: + break + log = log_int_fixed(p, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(p<>wp, wp) + pre = (u*cos) >> wp + pim = (u*sin) >> wp + basic_powers[p] = [(pre, pim)] + tre, tim = pre, pim + for m in range(1,int(math.log(a+n,p)+0.01)+1): + tre, tim = ((pre*tre-pim*tim)>>wp), ((pim*tre+pre*tim)>>wp) + basic_powers[p].append((tre,tim)) + xre = MPZ_ZERO + xim = MPZ_ZERO + if a == 1: + xre += one + aa = max(a,2) + for k in xrange(aa, a+n+1): + p = sieve[k] + if p in basic_powers: + m = mult[k] + tre, tim = basic_powers[p][m-1] + while 1: + k //= p**m + if k == 1: + break + p = sieve[k] + m = mult[k] + pre, pim = basic_powers[p][m-1] + tre, tim = ((pre*tre-pim*tim)>>wp), ((pim*tre+pre*tim)>>wp) + else: + log = log_int_fixed(k, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(k<>wp, wp) + tre = (u*cos) >> wp + tim = (u*sin) >> wp + xre += tre + xim += tim + return xre, xim + +# Set to something large to disable +ZETASUM_SIEVE_CUTOFF = 10 + +def mpc_zetasum(s, a, n, derivatives, reflect, prec): + """ + Fast version of mp._zetasum, assuming s = complex, a = integer. + """ + + wp = prec + 10 + derivatives = list(derivatives) + have_derivatives = derivatives != [0] + have_one_derivative = len(derivatives) == 1 + + # parse s + sre, sim = s + critical_line = (sre == fhalf) + sre = to_fixed(sre, wp) + sim = to_fixed(sim, wp) + + if a > 0 and n > ZETASUM_SIEVE_CUTOFF and not have_derivatives \ + and not reflect and (n < 4e7 or sys.maxsize > 2**32): + re, im = zetasum_sieved(critical_line, sre, sim, a, n, wp) + xs = [(from_man_exp(re, -wp, prec, 'n'), from_man_exp(im, -wp, prec, 'n'))] + return xs, [] + + maxd = max(derivatives) + if not have_one_derivative: + derivatives = range(maxd+1) + + # x_d = 0, y_d = 0 + xre = [MPZ_ZERO for d in derivatives] + xim = [MPZ_ZERO for d in derivatives] + if reflect: + yre = [MPZ_ZERO for d in derivatives] + yim = [MPZ_ZERO for d in derivatives] + else: + yre = yim = [] + + one = MPZ_ONE << wp + one_2wp = MPZ_ONE << (2*wp) + + ln2 = ln2_fixed(wp) + pi2 = pi_fixed(wp-1) + wp2 = wp+wp + + for w in xrange(a, a+n+1): + log = log_int_fixed(w, wp, ln2) + cos, sin = cos_sin_fixed((-sim*log)>>wp, wp, pi2) + if critical_line: + u = one_2wp // isqrt_fast(w<>wp, wp) + xterm_re = (u * cos) >> wp + xterm_im = (u * sin) >> wp + if reflect: + reciprocal = (one_2wp // (u*w)) + yterm_re = (reciprocal * cos) >> wp + yterm_im = (reciprocal * sin) >> wp + + if have_derivatives: + if have_one_derivative: + log = pow_fixed(log, maxd, wp) + xre[0] += (xterm_re * log) >> wp + xim[0] += (xterm_im * log) >> wp + if reflect: + yre[0] += (yterm_re * log) >> wp + yim[0] += (yterm_im * log) >> wp + else: + t = MPZ_ONE << wp + for d in derivatives: + xre[d] += (xterm_re * t) >> wp + xim[d] += (xterm_im * t) >> wp + if reflect: + yre[d] += (yterm_re * t) >> wp + yim[d] += (yterm_im * t) >> wp + t = (t * log) >> wp + else: + xre[0] += xterm_re + xim[0] += xterm_im + if reflect: + yre[0] += yterm_re + yim[0] += yterm_im + if have_derivatives: + if have_one_derivative: + if maxd % 2: + xre[0] = -xre[0] + xim[0] = -xim[0] + if reflect: + yre[0] = -yre[0] + yim[0] = -yim[0] + else: + xre = [(-1)**d * xre[d] for d in derivatives] + xim = [(-1)**d * xim[d] for d in derivatives] + if reflect: + yre = [(-1)**d * yre[d] for d in derivatives] + yim = [(-1)**d * yim[d] for d in derivatives] + xs = [(from_man_exp(xa, -wp, prec, 'n'), from_man_exp(xb, -wp, prec, 'n')) + for (xa, xb) in zip(xre, xim)] + ys = [(from_man_exp(ya, -wp, prec, 'n'), from_man_exp(yb, -wp, prec, 'n')) + for (ya, yb) in zip(yre, yim)] + return xs, ys + + +#-----------------------------------------------------------------------# +# # +# The gamma function (NEW IMPLEMENTATION) # +# # +#-----------------------------------------------------------------------# + +# Higher means faster, but more precomputation time +MAX_GAMMA_TAYLOR_PREC = 5000 +# Need to derive higher bounds for Taylor series to go higher +assert MAX_GAMMA_TAYLOR_PREC < 15000 + +# Use Stirling's series if abs(x) > beta*prec +# Important: must be large enough for convergence! +GAMMA_STIRLING_BETA = 0.2 + +SMALL_FACTORIAL_CACHE_SIZE = 150 + +gamma_taylor_cache = {} +gamma_stirling_cache = {} + +small_factorial_cache = [from_int(ifac(n)) for \ + n in range(SMALL_FACTORIAL_CACHE_SIZE+1)] + +def zeta_array(N, prec): + """ + zeta(n) = A * pi**n / n! + B + + where A is a rational number (A = Bernoulli number + for n even) and B is an infinite sum over powers of exp(2*pi). + (B = 0 for n even). + + TODO: this is currently only used for gamma, but could + be very useful elsewhere. + """ + extra = 30 + wp = prec+extra + zeta_values = [MPZ_ZERO] * (N+2) + pi = pi_fixed(wp) + # STEP 1: + one = MPZ_ONE << wp + zeta_values[0] = -one//2 + f_2pi = mpf_shift(mpf_pi(wp),1) + exp_2pi_k = exp_2pi = mpf_exp(f_2pi, wp) + # Compute exponential series + # Store values of 1/(exp(2*pi*k)-1), + # exp(2*pi*k)/(exp(2*pi*k)-1)**2, 1/(exp(2*pi*k)-1)**2 + # pi*k*exp(2*pi*k)/(exp(2*pi*k)-1)**2 + exps3 = [] + k = 1 + while 1: + tp = wp - 9*k + if tp < 1: + break + # 1/(exp(2*pi*k-1) + q1 = mpf_div(fone, mpf_sub(exp_2pi_k, fone, tp), tp) + # pi*k*exp(2*pi*k)/(exp(2*pi*k)-1)**2 + q2 = mpf_mul(exp_2pi_k, mpf_mul(q1,q1,tp), tp) + q1 = to_fixed(q1, wp) + q2 = to_fixed(q2, wp) + q2 = (k * q2 * pi) >> wp + exps3.append((q1, q2)) + # Multiply for next round + exp_2pi_k = mpf_mul(exp_2pi_k, exp_2pi, wp) + k += 1 + # Exponential sum + for n in xrange(3, N+1, 2): + s = MPZ_ZERO + k = 1 + for e1, e2 in exps3: + if n%4 == 3: + t = e1 // k**n + else: + U = (n-1)//4 + t = (e1 + e2//U) // k**n + if not t: + break + s += t + k += 1 + zeta_values[n] = -2*s + # Even zeta values + B = [mpf_abs(mpf_bernoulli(k,wp)) for k in xrange(N+2)] + pi_pow = fpi = mpf_pow_int(mpf_shift(mpf_pi(wp), 1), 2, wp) + pi_pow = mpf_div(pi_pow, from_int(4), wp) + for n in xrange(2,N+2,2): + z = mpf_mul(B[n], pi_pow, wp) + zeta_values[n] = to_fixed(z, wp) + pi_pow = mpf_mul(pi_pow, fpi, wp) + pi_pow = mpf_div(pi_pow, from_int((n+1)*(n+2)), wp) + # Zeta sum + reciprocal_pi = (one << wp) // pi + for n in xrange(3, N+1, 4): + U = (n-3)//4 + s = zeta_values[4*U+4]*(4*U+7)//4 + for k in xrange(1, U+1): + s -= (zeta_values[4*k] * zeta_values[4*U+4-4*k]) >> wp + zeta_values[n] += (2*s*reciprocal_pi) >> wp + for n in xrange(5, N+1, 4): + U = (n-1)//4 + s = zeta_values[4*U+2]*(2*U+1) + for k in xrange(1, 2*U+1): + s += ((-1)**k*2*k* zeta_values[2*k] * zeta_values[4*U+2-2*k])>>wp + zeta_values[n] += ((s*reciprocal_pi)>>wp)//(2*U) + return [x>>extra for x in zeta_values] + +def gamma_taylor_coefficients(inprec): + """ + Gives the Taylor coefficients of 1/gamma(1+x) as + a list of fixed-point numbers. Enough coefficients are returned + to ensure that the series converges to the given precision + when x is in [0.5, 1.5]. + """ + # Reuse nearby cache values (small case) + if inprec < 400: + prec = inprec + (10-(inprec%10)) + elif inprec < 1000: + prec = inprec + (30-(inprec%30)) + else: + prec = inprec + if prec in gamma_taylor_cache: + return gamma_taylor_cache[prec], prec + + # Experimentally determined bounds + if prec < 1000: + N = int(prec**0.76 + 2) + else: + # Valid to at least 15000 bits + N = int(prec**0.787 + 2) + + # Reuse higher precision values + for cprec in gamma_taylor_cache: + if cprec > prec: + coeffs = [x>>(cprec-prec) for x in gamma_taylor_cache[cprec][-N:]] + if inprec < 1000: + gamma_taylor_cache[prec] = coeffs + return coeffs, prec + + # Cache at a higher precision (large case) + if prec > 1000: + prec = int(prec * 1.2) + + wp = prec + 20 + A = [0] * N + A[0] = MPZ_ZERO + A[1] = MPZ_ONE << wp + A[2] = euler_fixed(wp) + # SLOW, reference implementation + #zeta_values = [0,0]+[to_fixed(mpf_zeta_int(k,wp),wp) for k in xrange(2,N)] + zeta_values = zeta_array(N, wp) + for k in xrange(3, N): + a = (-A[2]*A[k-1])>>wp + for j in xrange(2,k): + a += ((-1)**j * zeta_values[j] * A[k-j]) >> wp + a //= (1-k) + A[k] = a + A = [a>>20 for a in A] + A = A[::-1] + A = A[:-1] + gamma_taylor_cache[prec] = A + #return A, prec + return gamma_taylor_coefficients(inprec) + +def gamma_fixed_taylor(xmpf, x, wp, prec, rnd, type): + # Determine nearest multiple of N/2 + #n = int(x >> (wp-1)) + #steps = (n-1)>>1 + nearest_int = ((x >> (wp-1)) + MPZ_ONE) >> 1 + one = MPZ_ONE << wp + coeffs, cwp = gamma_taylor_coefficients(wp) + if nearest_int > 0: + r = one + for i in xrange(nearest_int-1): + x -= one + r = (r*x) >> wp + x -= one + p = MPZ_ZERO + for c in coeffs: + p = c + ((x*p)>>wp) + p >>= (cwp-wp) + if type == 0: + return from_man_exp((r<> wp + x += one + p = MPZ_ZERO + for c in coeffs: + p = c + ((x*p)>>wp) + p >>= (cwp-wp) + if wp - bitcount(abs(x)) > 10: + # pass very close to 0, so do floating-point multiply + g = mpf_add(xmpf, from_int(-nearest_int)) # exact + r = from_man_exp(p*r,-wp-wp) + r = mpf_mul(r, g, wp) + if type == 0: + return mpf_div(fone, r, prec, rnd) + if type == 2: + return mpf_pos(r, prec, rnd) + if type == 3: + return mpf_log(mpf_abs(mpf_div(fone, r, wp)), prec, rnd) + else: + r = from_man_exp(x*p*r,-3*wp) + if type == 0: return mpf_div(fone, r, prec, rnd) + if type == 2: return mpf_pos(r, prec, rnd) + if type == 3: return mpf_neg(mpf_log(mpf_abs(r), prec, rnd)) + +def stirling_coefficient(n): + if n in gamma_stirling_cache: + return gamma_stirling_cache[n] + p, q = bernfrac(n) + q *= MPZ(n*(n-1)) + gamma_stirling_cache[n] = p, q, bitcount(abs(p)), bitcount(q) + return gamma_stirling_cache[n] + +def real_stirling_series(x, prec): + """ + Sums the rational part of Stirling's expansion, + + log(sqrt(2*pi)) - z + 1/(12*z) - 1/(360*z^3) + ... + + """ + t = (MPZ_ONE<<(prec+prec)) // x # t = 1/x + u = (t*t)>>prec # u = 1/x**2 + s = ln_sqrt2pi_fixed(prec) - x + # Add initial terms of Stirling's series + s += t//12; t = (t*u)>>prec + s -= t//360; t = (t*u)>>prec + s += t//1260; t = (t*u)>>prec + s -= t//1680; t = (t*u)>>prec + if not t: return s + s += t//1188; t = (t*u)>>prec + s -= 691*t//360360; t = (t*u)>>prec + s += t//156; t = (t*u)>>prec + if not t: return s + s -= 3617*t//122400; t = (t*u)>>prec + s += 43867*t//244188; t = (t*u)>>prec + s -= 174611*t//125400; t = (t*u)>>prec + if not t: return s + k = 22 + # From here on, the coefficients are growing, so we + # have to keep t at a roughly constant size + usize = bitcount(abs(u)) + tsize = bitcount(abs(t)) + texp = 0 + while 1: + p, q, pb, qb = stirling_coefficient(k) + term_mag = tsize + pb + texp + shift = -texp + m = pb - term_mag + if m > 0 and shift < m: + p >>= m + shift -= m + m = tsize - term_mag + if m > 0 and shift < m: + w = t >> m + shift -= m + else: + w = t + term = (t*p//q) >> shift + if not term: + break + s += term + t = (t*u) >> usize + texp -= (prec - usize) + k += 2 + return s + +def complex_stirling_series(x, y, prec): + # t = 1/z + _m = (x*x + y*y) >> prec + tre = (x << prec) // _m + tim = (-y << prec) // _m + # u = 1/z**2 + ure = (tre*tre - tim*tim) >> prec + uim = tim*tre >> (prec-1) + # s = log(sqrt(2*pi)) - z + sre = ln_sqrt2pi_fixed(prec) - x + sim = -y + + # Add initial terms of Stirling's series + sre += tre//12; sim += tim//12; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= tre//360; sim -= tim//360; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += tre//1260; sim += tim//1260; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= tre//1680; sim -= tim//1680; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + sre += tre//1188; sim += tim//1188; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= 691*tre//360360; sim -= 691*tim//360360; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += tre//156; sim += tim//156; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + sre -= 3617*tre//122400; sim -= 3617*tim//122400; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre += 43867*tre//244188; sim += 43867*tim//244188; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + sre -= 174611*tre//125400; sim -= 174611*tim//125400; + tre, tim = ((tre*ure-tim*uim)>>prec), ((tre*uim+tim*ure)>>prec) + if abs(tre) + abs(tim) < 5: return sre, sim + + k = 22 + # From here on, the coefficients are growing, so we + # have to keep t at a roughly constant size + usize = bitcount(max(abs(ure), abs(uim))) + tsize = bitcount(max(abs(tre), abs(tim))) + texp = 0 + while 1: + p, q, pb, qb = stirling_coefficient(k) + term_mag = tsize + pb + texp + shift = -texp + m = pb - term_mag + if m > 0 and shift < m: + p >>= m + shift -= m + m = tsize - term_mag + if m > 0 and shift < m: + wre = tre >> m + wim = tim >> m + shift -= m + else: + wre = tre + wim = tim + termre = (tre*p//q) >> shift + termim = (tim*p//q) >> shift + if abs(termre) + abs(termim) < 5: + break + sre += termre + sim += termim + tre, tim = ((tre*ure - tim*uim)>>usize), \ + ((tre*uim + tim*ure)>>usize) + texp -= (prec - usize) + k += 2 + return sre, sim + + +def mpf_gamma(x, prec, rnd='d', type=0): + """ + This function implements multipurpose evaluation of the gamma + function, G(x), as well as the following versions of the same: + + type = 0 -- G(x) [standard gamma function] + type = 1 -- G(x+1) = x*G(x+1) = x! [factorial] + type = 2 -- 1/G(x) [reciprocal gamma function] + type = 3 -- log(|G(x)|) [log-gamma function, real part] + """ + + # Specal values + sign, man, exp, bc = x + if not man: + if x == fzero: + if type == 1: return fone + if type == 2: return fzero + raise ValueError("gamma function pole") + if x == finf: + if type == 2: return fzero + return finf + return fnan + + # First of all, for log gamma, numbers can be well beyond the fixed-point + # range, so we must take care of huge numbers before e.g. trying + # to convert x to the nearest integer + if type == 3: + wp = prec+20 + if exp+bc > wp and not sign: + return mpf_sub(mpf_mul(x, mpf_log(x, wp), wp), x, prec, rnd) + + # We strongly want to special-case small integers + is_integer = exp >= 0 + if is_integer: + # Poles + if sign: + if type == 2: + return fzero + raise ValueError("gamma function pole") + # n = x + n = man << exp + if n < SMALL_FACTORIAL_CACHE_SIZE: + if type == 0: + return mpf_pos(small_factorial_cache[n-1], prec, rnd) + if type == 1: + return mpf_pos(small_factorial_cache[n], prec, rnd) + if type == 2: + return mpf_div(fone, small_factorial_cache[n-1], prec, rnd) + if type == 3: + return mpf_log(small_factorial_cache[n-1], prec, rnd) + else: + # floor(abs(x)) + n = int(man >> (-exp)) + + # Estimate size and precision + # Estimate log(gamma(|x|),2) as x*log(x,2) + mag = exp + bc + gamma_size = n*mag + + if type == 3: + wp = prec + 20 + else: + wp = prec + bitcount(gamma_size) + 20 + + # Very close to 0, pole + if mag < -wp: + if type == 0: + return mpf_sub(mpf_div(fone,x, wp),mpf_shift(fone,-wp),prec,rnd) + if type == 1: return mpf_sub(fone, x, prec, rnd) + if type == 2: return mpf_add(x, mpf_shift(fone,mag-wp), prec, rnd) + if type == 3: return mpf_neg(mpf_log(mpf_abs(x), prec, rnd)) + + # From now on, we assume having a gamma function + if type == 1: + return mpf_gamma(mpf_add(x, fone), prec, rnd, 0) + + # Special case integers (those not small enough to be caught above, + # but still small enough for an exact factorial to be faster + # than an approximate algorithm), and half-integers + if exp >= -1: + if is_integer: + if gamma_size < 10*wp: + if type == 0: + return from_int(ifac(n-1), prec, rnd) + if type == 2: + return from_rational(MPZ_ONE, ifac(n-1), prec, rnd) + if type == 3: + return mpf_log(from_int(ifac(n-1)), prec, rnd) + # half-integer + if n < 100 or gamma_size < 10*wp: + if sign: + w = sqrtpi_fixed(wp) + if n % 2: f = ifac2(2*n+1) + else: f = -ifac2(2*n+1) + if type == 0: + return mpf_shift(from_rational(w, f, prec, rnd), -wp+n+1) + if type == 2: + return mpf_shift(from_rational(f, w, prec, rnd), wp-n-1) + if type == 3: + return mpf_log(mpf_shift(from_rational(w, abs(f), + prec, rnd), -wp+n+1), prec, rnd) + elif n == 0: + if type == 0: return mpf_sqrtpi(prec, rnd) + if type == 2: return mpf_div(fone, mpf_sqrtpi(wp), prec, rnd) + if type == 3: return mpf_log(mpf_sqrtpi(wp), prec, rnd) + else: + w = sqrtpi_fixed(wp) + w = from_man_exp(w * ifac2(2*n-1), -wp-n) + if type == 0: return mpf_pos(w, prec, rnd) + if type == 2: return mpf_div(fone, w, prec, rnd) + if type == 3: return mpf_log(mpf_abs(w), prec, rnd) + + # Convert to fixed point + offset = exp + wp + if offset >= 0: absxman = man << offset + else: absxman = man >> (-offset) + + # For log gamma, provide accurate evaluation for x = 1+eps and 2+eps + if type == 3 and not sign: + one = MPZ_ONE << wp + one_dist = abs(absxman-one) + two_dist = abs(absxman-2*one) + cancellation = (wp - bitcount(min(one_dist, two_dist))) + if cancellation > 10: + xsub1 = mpf_sub(fone, x) + xsub2 = mpf_sub(ftwo, x) + xsub1mag = xsub1[2]+xsub1[3] + xsub2mag = xsub2[2]+xsub2[3] + if xsub1mag < -wp: + return mpf_mul(mpf_euler(wp), mpf_sub(fone, x), prec, rnd) + if xsub2mag < -wp: + return mpf_mul(mpf_sub(fone, mpf_euler(wp)), + mpf_sub(x, ftwo), prec, rnd) + # Proceed but increase precision + wp += max(-xsub1mag, -xsub2mag) + offset = exp + wp + if offset >= 0: absxman = man << offset + else: absxman = man >> (-offset) + + # Use Taylor series if appropriate + n_for_stirling = int(GAMMA_STIRLING_BETA*wp) + if n < max(100, n_for_stirling) and wp < MAX_GAMMA_TAYLOR_PREC: + if sign: + absxman = -absxman + return gamma_fixed_taylor(x, absxman, wp, prec, rnd, type) + + # Use Stirling's series + # First ensure that |x| is large enough for rapid convergence + xorig = x + + # Argument reduction + r = 0 + if n < n_for_stirling: + r = one = MPZ_ONE << wp + d = n_for_stirling - n + for k in xrange(d): + r = (r * absxman) >> wp + absxman += one + x = xabs = from_man_exp(absxman, -wp) + if sign: + x = mpf_neg(x) + else: + xabs = mpf_abs(x) + + # Asymptotic series + y = real_stirling_series(absxman, wp) + u = to_fixed(mpf_log(xabs, wp), wp) + u = ((absxman - (MPZ_ONE<<(wp-1))) * u) >> wp + y += u + w = from_man_exp(y, -wp) + + # Compute final value + if sign: + # Reflection formula + A = mpf_mul(mpf_sin_pi(xorig, wp), xorig, wp) + B = mpf_neg(mpf_pi(wp)) + if type == 0 or type == 2: + A = mpf_mul(A, mpf_exp(w, wp)) + if r: + B = mpf_mul(B, from_man_exp(r, -wp), wp) + if type == 0: + return mpf_div(B, A, prec, rnd) + if type == 2: + return mpf_div(A, B, prec, rnd) + if type == 3: + if r: + B = mpf_mul(B, from_man_exp(r, -wp), wp) + A = mpf_add(mpf_log(mpf_abs(A), wp), w, wp) + return mpf_sub(mpf_log(mpf_abs(B), wp), A, prec, rnd) + else: + if type == 0: + if r: + return mpf_div(mpf_exp(w, wp), + from_man_exp(r, -wp), prec, rnd) + return mpf_exp(w, prec, rnd) + if type == 2: + if r: + return mpf_div(from_man_exp(r, -wp), + mpf_exp(w, wp), prec, rnd) + return mpf_exp(mpf_neg(w), prec, rnd) + if type == 3: + if r: + return mpf_sub(w, mpf_log(from_man_exp(r,-wp), wp), prec, rnd) + return mpf_pos(w, prec, rnd) + + +def mpc_gamma(z, prec, rnd='d', type=0): + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + + if b == fzero: + # Imaginary part on negative half-axis for log-gamma function + if type == 3 and asign: + re = mpf_gamma(a, prec, rnd, 3) + n = (-aman) >> (-aexp) + im = mpf_mul_int(mpf_pi(prec+10), n, prec, rnd) + return re, im + return mpf_gamma(a, prec, rnd, type), fzero + + # Some kind of complex inf/nan + if (not aman and aexp) or (not bman and bexp): + return (fnan, fnan) + + # Initial working precision + wp = prec + 20 + + amag = aexp+abc + bmag = bexp+bbc + if aman: + mag = max(amag, bmag) + else: + mag = bmag + + # Close to 0 + if mag < -8: + if mag < -wp: + # 1/gamma(z) = z + euler*z^2 + O(z^3) + v = mpc_add(z, mpc_mul_mpf(mpc_mul(z,z,wp),mpf_euler(wp),wp), wp) + if type == 0: return mpc_reciprocal(v, prec, rnd) + if type == 1: return mpc_div(z, v, prec, rnd) + if type == 2: return mpc_pos(v, prec, rnd) + if type == 3: return mpc_log(mpc_reciprocal(v, prec), prec, rnd) + elif type != 1: + wp += (-mag) + + # Handle huge log-gamma values; must do this before converting to + # a fixed-point value. TODO: determine a precise cutoff of validity + # depending on amag and bmag + if type == 3 and mag > wp and ((not asign) or (bmag >= amag)): + return mpc_sub(mpc_mul(z, mpc_log(z, wp), wp), z, prec, rnd) + + # From now on, we assume having a gamma function + if type == 1: + return mpc_gamma((mpf_add(a, fone), b), prec, rnd, 0) + + an = abs(to_int(a)) + bn = abs(to_int(b)) + absn = max(an, bn) + gamma_size = absn*mag + if type == 3: + pass + else: + wp += bitcount(gamma_size) + + # Reflect to the right half-plane. Note that Stirling's expansion + # is valid in the left half-plane too, as long as we're not too close + # to the real axis, but in order to use this argument reduction + # in the negative direction must be implemented. + #need_reflection = asign and ((bmag < 0) or (amag-bmag > 4)) + need_reflection = asign + zorig = z + if need_reflection: + z = mpc_neg(z) + asign, aman, aexp, abc = a = z[0] + bsign, bman, bexp, bbc = b = z[1] + + # Imaginary part very small compared to real one? + yfinal = 0 + balance_prec = 0 + if bmag < -10: + # Check z ~= 1 and z ~= 2 for loggamma + if type == 3: + zsub1 = mpc_sub_mpf(z, fone) + if zsub1[0] == fzero: + cancel1 = -bmag + else: + cancel1 = -max(zsub1[0][2]+zsub1[0][3], bmag) + if cancel1 > wp: + pi = mpf_pi(wp) + x = mpc_mul_mpf(zsub1, pi, wp) + x = mpc_mul(x, x, wp) + x = mpc_div_mpf(x, from_int(12), wp) + y = mpc_mul_mpf(zsub1, mpf_neg(mpf_euler(wp)), wp) + yfinal = mpc_add(x, y, wp) + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + elif cancel1 > 0: + wp += cancel1 + zsub2 = mpc_sub_mpf(z, ftwo) + if zsub2[0] == fzero: + cancel2 = -bmag + else: + cancel2 = -max(zsub2[0][2]+zsub2[0][3], bmag) + if cancel2 > wp: + pi = mpf_pi(wp) + t = mpf_sub(mpf_mul(pi, pi), from_int(6)) + x = mpc_mul_mpf(mpc_mul(zsub2, zsub2, wp), t, wp) + x = mpc_div_mpf(x, from_int(12), wp) + y = mpc_mul_mpf(zsub2, mpf_sub(fone, mpf_euler(wp)), wp) + yfinal = mpc_add(x, y, wp) + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + elif cancel2 > 0: + wp += cancel2 + if bmag < -wp: + # Compute directly from the real gamma function. + pp = 2*(wp+10) + aabs = mpf_abs(a) + eps = mpf_shift(fone, amag-wp) + x1 = mpf_gamma(aabs, pp, type=type) + x2 = mpf_gamma(mpf_add(aabs, eps), pp, type=type) + xprime = mpf_div(mpf_sub(x2, x1, pp), eps, pp) + y = mpf_mul(b, xprime, prec, rnd) + yfinal = (x1, y) + # Note: we still need to use the reflection formula for + # near-poles, and the correct branch of the log-gamma function + if not need_reflection: + return mpc_pos(yfinal, prec, rnd) + else: + balance_prec += (-bmag) + + wp += balance_prec + n_for_stirling = int(GAMMA_STIRLING_BETA*wp) + need_reduction = absn < n_for_stirling + + afix = to_fixed(a, wp) + bfix = to_fixed(b, wp) + + r = 0 + if not yfinal: + zprered = z + # Argument reduction + if absn < n_for_stirling: + absn = complex(an, bn) + d = int((1 + n_for_stirling**2 - bn**2)**0.5 - an) + rre = one = MPZ_ONE << wp + rim = MPZ_ZERO + for k in xrange(d): + rre, rim = ((afix*rre-bfix*rim)>>wp), ((afix*rim + bfix*rre)>>wp) + afix += one + r = from_man_exp(rre, -wp), from_man_exp(rim, -wp) + a = from_man_exp(afix, -wp) + z = a, b + + yre, yim = complex_stirling_series(afix, bfix, wp) + # (z-1/2)*log(z) + S + lre, lim = mpc_log(z, wp) + lre = to_fixed(lre, wp) + lim = to_fixed(lim, wp) + yre = ((lre*afix - lim*bfix)>>wp) - (lre>>1) + yre + yim = ((lre*bfix + lim*afix)>>wp) - (lim>>1) + yim + y = from_man_exp(yre, -wp), from_man_exp(yim, -wp) + + if r and type == 3: + # If re(z) > 0 and abs(z) <= 4, the branches of loggamma(z) + # and log(gamma(z)) coincide. Otherwise, use the zeroth order + # Stirling expansion to compute the correct imaginary part. + y = mpc_sub(y, mpc_log(r, wp), wp) + zfa = to_float(zprered[0]) + zfb = to_float(zprered[1]) + zfabs = math.hypot(zfa,zfb) + #if not (zfa > 0.0 and zfabs <= 4): + yfb = to_float(y[1]) + u = math.atan2(zfb, zfa) + if zfabs <= 0.5: + gi = 0.577216*zfb - u + else: + gi = -zfb - 0.5*u + zfa*u + zfb*math.log(zfabs) + n = int(math.floor((gi-yfb)/(2*math.pi)+0.5)) + y = (y[0], mpf_add(y[1], mpf_mul_int(mpf_pi(wp), 2*n, wp), wp)) + + if need_reflection: + if type == 0 or type == 2: + A = mpc_mul(mpc_sin_pi(zorig, wp), zorig, wp) + B = (mpf_neg(mpf_pi(wp)), fzero) + if yfinal: + if type == 2: + A = mpc_div(A, yfinal, wp) + else: + A = mpc_mul(A, yfinal, wp) + else: + A = mpc_mul(A, mpc_exp(y, wp), wp) + if r: + B = mpc_mul(B, r, wp) + if type == 0: return mpc_div(B, A, prec, rnd) + if type == 2: return mpc_div(A, B, prec, rnd) + + # Reflection formula for the log-gamma function with correct branch + # http://functions.wolfram.com/GammaBetaErf/LogGamma/16/01/01/0006/ + # LogGamma[z] == -LogGamma[-z] - Log[-z] + + # Sign[Im[z]] Floor[Re[z]] Pi I + Log[Pi] - + # Log[Sin[Pi (z - Floor[Re[z]])]] - + # Pi I (1 - Abs[Sign[Im[z]]]) Abs[Floor[Re[z]]] + if type == 3: + if yfinal: + s1 = mpc_neg(yfinal) + else: + s1 = mpc_neg(y) + # s -= log(-z) + s1 = mpc_sub(s1, mpc_log(mpc_neg(zorig), wp), wp) + # floor(re(z)) + rezfloor = mpf_floor(zorig[0]) + imzsign = mpf_sign(zorig[1]) + pi = mpf_pi(wp) + t = mpf_mul(pi, rezfloor) + t = mpf_mul_int(t, imzsign, wp) + s1 = (s1[0], mpf_add(s1[1], t, wp)) + s1 = mpc_add_mpf(s1, mpf_log(pi, wp), wp) + t = mpc_sin_pi(mpc_sub_mpf(zorig, rezfloor), wp) + t = mpc_log(t, wp) + s1 = mpc_sub(s1, t, wp) + # Note: may actually be unused, because we fall back + # to the mpf_ function for real arguments + if not imzsign: + t = mpf_mul(pi, mpf_floor(rezfloor), wp) + s1 = (s1[0], mpf_sub(s1[1], t, wp)) + return mpc_pos(s1, prec, rnd) + else: + if type == 0: + if r: + return mpc_div(mpc_exp(y, wp), r, prec, rnd) + return mpc_exp(y, prec, rnd) + if type == 2: + if r: + return mpc_div(r, mpc_exp(y, wp), prec, rnd) + return mpc_exp(mpc_neg(y), prec, rnd) + if type == 3: + return mpc_pos(y, prec, rnd) + +def mpf_factorial(x, prec, rnd='d'): + return mpf_gamma(x, prec, rnd, 1) + +def mpc_factorial(x, prec, rnd='d'): + return mpc_gamma(x, prec, rnd, 1) + +def mpf_rgamma(x, prec, rnd='d'): + return mpf_gamma(x, prec, rnd, 2) + +def mpc_rgamma(x, prec, rnd='d'): + return mpc_gamma(x, prec, rnd, 2) + +def mpf_loggamma(x, prec, rnd='d'): + sign, man, exp, bc = x + if sign: + raise ComplexResult + return mpf_gamma(x, prec, rnd, 3) + +def mpc_loggamma(z, prec, rnd='d'): + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero and asign: + re = mpf_gamma(a, prec, rnd, 3) + n = (-aman) >> (-aexp) + im = mpf_mul_int(mpf_pi(prec+10), n, prec, rnd) + return re, im + return mpc_gamma(z, prec, rnd, 3) + +def mpf_gamma_int(n, prec, rnd=round_fast): + if n < SMALL_FACTORIAL_CACHE_SIZE: + return mpf_pos(small_factorial_cache[n-1], prec, rnd) + return mpf_gamma(from_int(n), prec, rnd) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libelefun.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libelefun.py new file mode 100644 index 0000000000000000000000000000000000000000..3de2e5aaef02296ed03dec3df3021b56823f3728 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libelefun.py @@ -0,0 +1,1428 @@ +""" +This module implements computation of elementary transcendental +functions (powers, logarithms, trigonometric and hyperbolic +functions, inverse trigonometric and hyperbolic) for real +floating-point numbers. + +For complex and interval implementations of the same functions, +see libmpc and libmpi. + +""" + +import math +from bisect import bisect + +from .backend import xrange +from .backend import MPZ, MPZ_ZERO, MPZ_ONE, MPZ_TWO, MPZ_FIVE, BACKEND + +from .libmpf import ( + round_floor, round_ceiling, round_down, round_up, + round_nearest, round_fast, + ComplexResult, + bitcount, bctable, lshift, rshift, giant_steps, sqrt_fixed, + from_int, to_int, from_man_exp, to_fixed, to_float, from_float, + from_rational, normalize, + fzero, fone, fnone, fhalf, finf, fninf, fnan, + mpf_cmp, mpf_sign, mpf_abs, + mpf_pos, mpf_neg, mpf_add, mpf_sub, mpf_mul, mpf_div, mpf_shift, + mpf_rdiv_int, mpf_pow_int, mpf_sqrt, + reciprocal_rnd, negative_rnd, mpf_perturb, + isqrt_fast +) + +from .libintmath import ifib + + +#------------------------------------------------------------------------------- +# Tuning parameters +#------------------------------------------------------------------------------- + +# Cutoff for computing exp from cosh+sinh. This reduces the +# number of terms by half, but also requires a square root which +# is expensive with the pure-Python square root code. +if BACKEND == 'python': + EXP_COSH_CUTOFF = 600 +else: + EXP_COSH_CUTOFF = 400 +# Cutoff for using more than 2 series +EXP_SERIES_U_CUTOFF = 1500 + +# Also basically determined by sqrt +if BACKEND == 'python': + COS_SIN_CACHE_PREC = 400 +else: + COS_SIN_CACHE_PREC = 200 +COS_SIN_CACHE_STEP = 8 +cos_sin_cache = {} + +# Number of integer logarithms to cache (for zeta sums) +MAX_LOG_INT_CACHE = 2000 +log_int_cache = {} + +LOG_TAYLOR_PREC = 2500 # Use Taylor series with caching up to this prec +LOG_TAYLOR_SHIFT = 9 # Cache log values in steps of size 2^-N +log_taylor_cache = {} +# prec/size ratio of x for fastest convergence in AGM formula +LOG_AGM_MAG_PREC_RATIO = 20 + +ATAN_TAYLOR_PREC = 3000 # Same as for log +ATAN_TAYLOR_SHIFT = 7 # steps of size 2^-N +atan_taylor_cache = {} + + +# ~= next power of two + 20 +cache_prec_steps = [22,22] +for k in xrange(1, bitcount(LOG_TAYLOR_PREC)+1): + cache_prec_steps += [min(2**k,LOG_TAYLOR_PREC)+20] * 2**(k-1) + + +#----------------------------------------------------------------------------# +# # +# Elementary mathematical constants # +# # +#----------------------------------------------------------------------------# + +def constant_memo(f): + """ + Decorator for caching computed values of mathematical + constants. This decorator should be applied to a + function taking a single argument prec as input and + returning a fixed-point value with the given precision. + """ + f.memo_prec = -1 + f.memo_val = None + def g(prec, **kwargs): + memo_prec = f.memo_prec + if prec <= memo_prec: + return f.memo_val >> (memo_prec-prec) + newprec = int(prec*1.05+10) + f.memo_val = f(newprec, **kwargs) + f.memo_prec = newprec + return f.memo_val >> (newprec-prec) + g.__name__ = f.__name__ + g.__doc__ = f.__doc__ + return g + +def def_mpf_constant(fixed): + """ + Create a function that computes the mpf value for a mathematical + constant, given a function that computes the fixed-point value. + + Assumptions: the constant is positive and has magnitude ~= 1; + the fixed-point function rounds to floor. + """ + def f(prec, rnd=round_fast): + wp = prec + 20 + v = fixed(wp) + if rnd in (round_up, round_ceiling): + v += 1 + return normalize(0, v, -wp, bitcount(v), prec, rnd) + f.__doc__ = fixed.__doc__ + return f + +def bsp_acot(q, a, b, hyperbolic): + if b - a == 1: + a1 = MPZ(2*a + 3) + if hyperbolic or a&1: + return MPZ_ONE, a1 * q**2, a1 + else: + return -MPZ_ONE, a1 * q**2, a1 + m = (a+b)//2 + p1, q1, r1 = bsp_acot(q, a, m, hyperbolic) + p2, q2, r2 = bsp_acot(q, m, b, hyperbolic) + return q2*p1 + r1*p2, q1*q2, r1*r2 + +# the acoth(x) series converges like the geometric series for x^2 +# N = ceil(p*log(2)/(2*log(x))) +def acot_fixed(a, prec, hyperbolic): + """ + Compute acot(a) or acoth(a) for an integer a with binary splitting; see + http://numbers.computation.free.fr/Constants/Algorithms/splitting.html + """ + N = int(0.35 * prec/math.log(a) + 20) + p, q, r = bsp_acot(a, 0,N, hyperbolic) + return ((p+q)<> extraprec) + +# Logarithms of integers are needed for various computations involving +# logarithms, powers, radix conversion, etc + +@constant_memo +def ln2_fixed(prec): + """ + Computes ln(2). This is done with a hyperbolic Machin-type formula, + with binary splitting at high precision. + """ + return machin([(18, 26), (-2, 4801), (8, 8749)], prec, True) + +@constant_memo +def ln10_fixed(prec): + """ + Computes ln(10). This is done with a hyperbolic Machin-type formula. + """ + return machin([(46, 31), (34, 49), (20, 161)], prec, True) + + +r""" +For computation of pi, we use the Chudnovsky series: + + oo + ___ k + 1 \ (-1) (6 k)! (A + B k) + ----- = ) ----------------------- + 12 pi /___ 3 3k+3/2 + (3 k)! (k!) C + k = 0 + +where A, B, and C are certain integer constants. This series adds roughly +14 digits per term. Note that C^(3/2) can be extracted so that the +series contains only rational terms. This makes binary splitting very +efficient. + +The recurrence formulas for the binary splitting were taken from +ftp://ftp.gmplib.org/pub/src/gmp-chudnovsky.c + +Previously, Machin's formula was used at low precision and the AGM iteration +was used at high precision. However, the Chudnovsky series is essentially as +fast as the Machin formula at low precision and in practice about 3x faster +than the AGM at high precision (despite theoretically having a worse +asymptotic complexity), so there is no reason not to use it in all cases. + +""" + +# Constants in Chudnovsky's series +CHUD_A = MPZ(13591409) +CHUD_B = MPZ(545140134) +CHUD_C = MPZ(640320) +CHUD_D = MPZ(12) + +def bs_chudnovsky(a, b, level, verbose): + """ + Computes the sum from a to b of the series in the Chudnovsky + formula. Returns g, p, q where p/q is the sum as an exact + fraction and g is a temporary value used to save work + for recursive calls. + """ + if b-a == 1: + g = MPZ((6*b-5)*(2*b-1)*(6*b-1)) + p = b**3 * CHUD_C**3 // 24 + q = (-1)**b * g * (CHUD_A+CHUD_B*b) + else: + if verbose and level < 4: + print(" binary splitting", a, b) + mid = (a+b)//2 + g1, p1, q1 = bs_chudnovsky(a, mid, level+1, verbose) + g2, p2, q2 = bs_chudnovsky(mid, b, level+1, verbose) + p = p1*p2 + g = g1*g2 + q = q1*p2 + q2*g1 + return g, p, q + +@constant_memo +def pi_fixed(prec, verbose=False, verbose_base=None): + """ + Compute floor(pi * 2**prec) as a big integer. + + This is done using Chudnovsky's series (see comments in + libelefun.py for details). + """ + # The Chudnovsky series gives 14.18 digits per term + N = int(prec/3.3219280948/14.181647462 + 2) + if verbose: + print("binary splitting with N =", N) + g, p, q = bs_chudnovsky(0, N, 0, verbose) + sqrtC = isqrt_fast(CHUD_C<<(2*prec)) + v = p*CHUD_C*sqrtC//((q+CHUD_A*p)*CHUD_D) + return v + +def degree_fixed(prec): + return pi_fixed(prec)//180 + +def bspe(a, b): + """ + Sum series for exp(1)-1 between a, b, returning the result + as an exact fraction (p, q). + """ + if b-a == 1: + return MPZ_ONE, MPZ(b) + m = (a+b)//2 + p1, q1 = bspe(a, m) + p2, q2 = bspe(m, b) + return p1*q2+p2, q1*q2 + +@constant_memo +def e_fixed(prec): + """ + Computes exp(1). This is done using the ordinary Taylor series for + exp, with binary splitting. For a description of the algorithm, + see: + + http://numbers.computation.free.fr/Constants/ + Algorithms/splitting.html + """ + # Slight overestimate of N needed for 1/N! < 2**(-prec) + # This could be tightened for large N. + N = int(1.1*prec/math.log(prec) + 20) + p, q = bspe(0,N) + return ((p+q)<> 11 + +mpf_phi = def_mpf_constant(phi_fixed) +mpf_pi = def_mpf_constant(pi_fixed) +mpf_e = def_mpf_constant(e_fixed) +mpf_degree = def_mpf_constant(degree_fixed) +mpf_ln2 = def_mpf_constant(ln2_fixed) +mpf_ln10 = def_mpf_constant(ln10_fixed) + + +@constant_memo +def ln_sqrt2pi_fixed(prec): + wp = prec + 10 + # ln(sqrt(2*pi)) = ln(2*pi)/2 + return to_fixed(mpf_log(mpf_shift(mpf_pi(wp), 1), wp), prec-1) + +@constant_memo +def sqrtpi_fixed(prec): + return sqrt_fixed(pi_fixed(prec), prec) + +mpf_sqrtpi = def_mpf_constant(sqrtpi_fixed) +mpf_ln_sqrt2pi = def_mpf_constant(ln_sqrt2pi_fixed) + + +#----------------------------------------------------------------------------# +# # +# Powers # +# # +#----------------------------------------------------------------------------# + +def mpf_pow(s, t, prec, rnd=round_fast): + """ + Compute s**t. Raises ComplexResult if s is negative and t is + fractional. + """ + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + if ssign and texp < 0: + raise ComplexResult("negative number raised to a fractional power") + if texp >= 0: + return mpf_pow_int(s, (-1)**tsign * (tman<> pbc)] + if pbc > workprec: + pm = pm >> (pbc-workprec) + pe += pbc - workprec + pbc = workprec + n -= 1 + if not n: + break + y = y*y + exp = exp+exp + bc = bc + bc - 2 + bc = bc + bctable[int(y >> bc)] + if bc > workprec: + y = y >> (bc-workprec) + exp += bc - workprec + bc = workprec + n = n // 2 + return pm, pe + +# froot(s, n, prec, rnd) computes the real n-th root of a +# positive mpf tuple s. +# To compute the root we start from a 50-bit estimate for r +# generated with ordinary floating-point arithmetic, and then refine +# the value to full accuracy using the iteration + +# 1 / y \ +# r = --- | (n-1) * r + ---------- | +# n+1 n \ n r_n**(n-1) / + +# which is simply Newton's method applied to the equation r**n = y. +# With giant_steps(start, prec+extra) = [p0,...,pm, prec+extra] +# and y = man * 2**-shift one has +# (man * 2**exp)**(1/n) = +# y**(1/n) * 2**(start-prec/n) * 2**(p0-start) * ... * 2**(prec+extra-pm) * +# 2**((exp+shift-(n-1)*prec)/n -extra)) +# The last factor is accounted for in the last line of froot. + +def nthroot_fixed(y, n, prec, exp1): + start = 50 + try: + y1 = rshift(y, prec - n*start) + r = MPZ(int(y1**(1.0/n))) + except OverflowError: + y1 = from_int(y1, start) + fn = from_int(n) + fn = mpf_rdiv_int(1, fn, start) + r = mpf_pow(y1, fn, start) + r = to_int(r) + extra = 10 + extra1 = n + prevp = start + for p in giant_steps(start, prec+extra): + pm, pe = int_pow_fixed(r, n-1, prevp) + r2 = rshift(pm, (n-1)*prevp - p - pe - extra1) + B = lshift(y, 2*p-prec+extra1)//r2 + r = (B + (n-1) * lshift(r, p-prevp))//n + prevp = p + return r + +def mpf_nthroot(s, n, prec, rnd=round_fast): + """nth-root of a positive number + + Use the Newton method when faster, otherwise use x**(1/n) + """ + sign, man, exp, bc = s + if sign: + raise ComplexResult("nth root of a negative number") + if not man: + if s == fnan: + return fnan + if s == fzero: + if n > 0: + return fzero + if n == 0: + return fone + return finf + # Infinity + if not n: + return fnan + if n < 0: + return fzero + return finf + flag_inverse = False + if n < 2: + if n == 0: + return fone + if n == 1: + return mpf_pos(s, prec, rnd) + if n == -1: + return mpf_div(fone, s, prec, rnd) + # n < 0 + rnd = reciprocal_rnd[rnd] + flag_inverse = True + extra_inverse = 5 + prec += extra_inverse + n = -n + if n > 20 and (n >= 20000 or prec < int(233 + 28.3 * n**0.62)): + prec2 = prec + 10 + fn = from_int(n) + nth = mpf_rdiv_int(1, fn, prec2) + r = mpf_pow(s, nth, prec2, rnd) + s = normalize(r[0], r[1], r[2], r[3], prec, rnd) + if flag_inverse: + return mpf_div(fone, s, prec-extra_inverse, rnd) + else: + return s + # Convert to a fixed-point number with prec2 bits. + prec2 = prec + 2*n - (prec%n) + # a few tests indicate that + # for 10 < n < 10**4 a bit more precision is needed + if n > 10: + prec2 += prec2//10 + prec2 = prec2 - prec2%n + # Mantissa may have more bits than we need. Trim it down. + shift = bc - prec2 + # Adjust exponents to make prec2 and exp+shift multiples of n. + sign1 = 0 + es = exp+shift + if es < 0: + sign1 = 1 + es = -es + if sign1: + shift += es%n + else: + shift -= es%n + man = rshift(man, shift) + extra = 10 + exp1 = ((exp+shift-(n-1)*prec2)//n) - extra + rnd_shift = 0 + if flag_inverse: + if rnd == 'u' or rnd == 'c': + rnd_shift = 1 + else: + if rnd == 'd' or rnd == 'f': + rnd_shift = 1 + man = nthroot_fixed(man+rnd_shift, n, prec2, exp1) + s = from_man_exp(man, exp1, prec, rnd) + if flag_inverse: + return mpf_div(fone, s, prec-extra_inverse, rnd) + else: + return s + +def mpf_cbrt(s, prec, rnd=round_fast): + """cubic root of a positive number""" + return mpf_nthroot(s, 3, prec, rnd) + +#----------------------------------------------------------------------------# +# # +# Logarithms # +# # +#----------------------------------------------------------------------------# + + +def log_int_fixed(n, prec, ln2=None): + """ + Fast computation of log(n), caching the value for small n, + intended for zeta sums. + """ + if n in log_int_cache: + value, vprec = log_int_cache[n] + if vprec >= prec: + return value >> (vprec - prec) + wp = prec + 10 + if wp <= LOG_TAYLOR_SHIFT: + if ln2 is None: + ln2 = ln2_fixed(wp) + r = bitcount(n) + x = n << (wp-r) + v = log_taylor_cached(x, wp) + r*ln2 + else: + v = to_fixed(mpf_log(from_int(n), wp+5), wp) + if n < MAX_LOG_INT_CACHE: + log_int_cache[n] = (v, wp) + return v >> (wp-prec) + +def agm_fixed(a, b, prec): + """ + Fixed-point computation of agm(a,b), assuming + a, b both close to unit magnitude. + """ + i = 0 + while 1: + anew = (a+b)>>1 + if i > 4 and abs(a-anew) < 8: + return a + b = isqrt_fast(a*b) + a = anew + i += 1 + return a + +def log_agm(x, prec): + """ + Fixed-point computation of -log(x) = log(1/x), suitable + for large precision. It is required that 0 < x < 1. The + algorithm used is the Sasaki-Kanada formula + + -log(x) = pi/agm(theta2(x)^2,theta3(x)^2). [1] + + For faster convergence in the theta functions, x should + be chosen closer to 0. + + Guard bits must be added by the caller. + + HYPOTHESIS: if x = 2^(-n), n bits need to be added to + account for the truncation to a fixed-point number, + and this is the only significant cancellation error. + + The number of bits lost to roundoff is small and can be + considered constant. + + [1] Richard P. Brent, "Fast Algorithms for High-Precision + Computation of Elementary Functions (extended abstract)", + http://wwwmaths.anu.edu.au/~brent/pd/RNC7-Brent.pdf + + """ + x2 = (x*x) >> prec + # Compute jtheta2(x)**2 + s = a = b = x2 + while a: + b = (b*x2) >> prec + a = (a*b) >> prec + s += a + s += (MPZ_ONE<>(prec-2) + s = (s*isqrt_fast(x<>prec + # Compute jtheta3(x)**2 + t = a = b = x + while a: + b = (b*x2) >> prec + a = (a*b) >> prec + t += a + t = (MPZ_ONE<>prec + # Final formula + p = agm_fixed(s, t, prec) + return (pi_fixed(prec) << prec) // p + +def log_taylor(x, prec, r=0): + """ + Fixed-point calculation of log(x). It is assumed that x is close + enough to 1 for the Taylor series to converge quickly. Convergence + can be improved by specifying r > 0 to compute + log(x^(1/2^r))*2^r, at the cost of performing r square roots. + + The caller must provide sufficient guard bits. + """ + for i in xrange(r): + x = isqrt_fast(x<> prec + v4 = (v2*v2) >> prec + s0 = v + s1 = v//3 + v = (v*v4) >> prec + k = 5 + while v: + s0 += v // k + k += 2 + s1 += v // k + v = (v*v4) >> prec + k += 2 + s1 = (s1*v2) >> prec + s = (s0+s1) << (1+r) + if sign: + return -s + return s + +def log_taylor_cached(x, prec): + """ + Fixed-point computation of log(x), assuming x in (0.5, 2) + and prec <= LOG_TAYLOR_PREC. + """ + n = x >> (prec-LOG_TAYLOR_SHIFT) + cached_prec = cache_prec_steps[prec] + dprec = cached_prec - prec + if (n, cached_prec) in log_taylor_cache: + a, log_a = log_taylor_cache[n, cached_prec] + else: + a = n << (cached_prec - LOG_TAYLOR_SHIFT) + log_a = log_taylor(a, cached_prec, 8) + log_taylor_cache[n, cached_prec] = (a, log_a) + a >>= dprec + log_a >>= dprec + u = ((x - a) << prec) // a + v = (u << prec) // ((MPZ_TWO << prec) + u) + v2 = (v*v) >> prec + v4 = (v2*v2) >> prec + s0 = v + s1 = v//3 + v = (v*v4) >> prec + k = 5 + while v: + s0 += v//k + k += 2 + s1 += v//k + v = (v*v4) >> prec + k += 2 + s1 = (s1*v2) >> prec + s = (s0+s1) << 1 + return log_a + s + +def mpf_log(x, prec, rnd=round_fast): + """ + Compute the natural logarithm of the mpf value x. If x is negative, + ComplexResult is raised. + """ + sign, man, exp, bc = x + #------------------------------------------------------------------ + # Handle special values + if not man: + if x == fzero: return fninf + if x == finf: return finf + if x == fnan: return fnan + if sign: + raise ComplexResult("logarithm of a negative number") + wp = prec + 20 + #------------------------------------------------------------------ + # Handle log(2^n) = log(n)*2. + # Here we catch the only possible exact value, log(1) = 0 + if man == 1: + if not exp: + return fzero + return from_man_exp(exp*ln2_fixed(wp), -wp, prec, rnd) + mag = exp+bc + abs_mag = abs(mag) + #------------------------------------------------------------------ + # Handle x = 1+eps, where log(x) ~ x. We need to check for + # cancellation when moving to fixed-point math and compensate + # by increasing the precision. Note that abs_mag in (0, 1) <=> + # 0.5 < x < 2 and x != 1 + if abs_mag <= 1: + # Calculate t = x-1 to measure distance from 1 in bits + tsign = 1-abs_mag + if tsign: + tman = (MPZ_ONE< wp: + t = normalize(tsign, tman, abs_mag-bc, tbc, tbc, 'n') + return mpf_perturb(t, tsign, prec, rnd) + else: + wp += cancellation + # TODO: if close enough to 1, we could use Taylor series + # even in the AGM precision range, since the Taylor series + # converges rapidly + #------------------------------------------------------------------ + # Another special case: + # n*log(2) is a good enough approximation + if abs_mag > 10000: + if bitcount(abs_mag) > wp: + return from_man_exp(exp*ln2_fixed(wp), -wp, prec, rnd) + #------------------------------------------------------------------ + # General case. + # Perform argument reduction using log(x) = log(x*2^n) - n*log(2): + # If we are in the Taylor precision range, choose magnitude 0 or 1. + # If we are in the AGM precision range, choose magnitude -m for + # some large m; benchmarking on one machine showed m = prec/20 to be + # optimal between 1000 and 100,000 digits. + if wp <= LOG_TAYLOR_PREC: + m = log_taylor_cached(lshift(man, wp-bc), wp) + if mag: + m += mag*ln2_fixed(wp) + else: + optimal_mag = -wp//LOG_AGM_MAG_PREC_RATIO + n = optimal_mag - mag + x = mpf_shift(x, n) + wp += (-optimal_mag) + m = -log_agm(to_fixed(x, wp), wp) + m -= n*ln2_fixed(wp) + return from_man_exp(m, -wp, prec, rnd) + +def mpf_log_hypot(a, b, prec, rnd): + """ + Computes log(sqrt(a^2+b^2)) accurately. + """ + # If either a or b is inf/nan/0, assume it to be a + if not b[1]: + a, b = b, a + # a is inf/nan/0 + if not a[1]: + # both are inf/nan/0 + if not b[1]: + if a == b == fzero: + return fninf + if fnan in (a, b): + return fnan + # at least one term is (+/- inf)^2 + return finf + # only a is inf/nan/0 + if a == fzero: + # log(sqrt(0+b^2)) = log(|b|) + return mpf_log(mpf_abs(b), prec, rnd) + if a == fnan: + return fnan + return finf + # Exact + a2 = mpf_mul(a,a) + b2 = mpf_mul(b,b) + extra = 20 + # Not exact + h2 = mpf_add(a2, b2, prec+extra) + cancelled = mpf_add(h2, fnone, 10) + mag_cancelled = cancelled[2]+cancelled[3] + # Just redo the sum exactly if necessary (could be smarter + # and avoid memory allocation when a or b is precisely 1 + # and the other is tiny...) + if cancelled == fzero or mag_cancelled < -extra//2: + h2 = mpf_add(a2, b2, prec+extra-min(a2[2],b2[2])) + return mpf_shift(mpf_log(h2, prec, rnd), -1) + + +#---------------------------------------------------------------------- +# Inverse tangent +# + +def atan_newton(x, prec): + if prec >= 100: + r = math.atan(int((x>>(prec-53)))/2.0**53) + else: + r = math.atan(int(x)/2.0**prec) + prevp = 50 + r = MPZ(int(r * 2.0**53) >> (53-prevp)) + extra_p = 50 + for wp in giant_steps(prevp, prec): + wp += extra_p + r = r << (wp-prevp) + cos, sin = cos_sin_fixed(r, wp) + tan = (sin << wp) // cos + a = ((tan-rshift(x, prec-wp)) << wp) // ((MPZ_ONE<>wp)) + r = r - a + prevp = wp + return rshift(r, prevp-prec) + +def atan_taylor_get_cached(n, prec): + # Taylor series with caching wins up to huge precisions + # To avoid unnecessary precomputation at low precision, we + # do it in steps + # Round to next power of 2 + prec2 = (1<<(bitcount(prec-1))) + 20 + dprec = prec2 - prec + if (n, prec2) in atan_taylor_cache: + a, atan_a = atan_taylor_cache[n, prec2] + else: + a = n << (prec2 - ATAN_TAYLOR_SHIFT) + atan_a = atan_newton(a, prec2) + atan_taylor_cache[n, prec2] = (a, atan_a) + return (a >> dprec), (atan_a >> dprec) + +def atan_taylor(x, prec): + n = (x >> (prec-ATAN_TAYLOR_SHIFT)) + a, atan_a = atan_taylor_get_cached(n, prec) + d = x - a + s0 = v = (d << prec) // ((a**2 >> prec) + (a*d >> prec) + (MPZ_ONE << prec)) + v2 = (v**2 >> prec) + v4 = (v2 * v2) >> prec + s1 = v//3 + v = (v * v4) >> prec + k = 5 + while v: + s0 += v // k + k += 2 + s1 += v // k + v = (v * v4) >> prec + k += 2 + s1 = (s1 * v2) >> prec + s = s0 - s1 + return atan_a + s + +def atan_inf(sign, prec, rnd): + if not sign: + return mpf_shift(mpf_pi(prec, rnd), -1) + return mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + +def mpf_atan(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if not man: + if x == fzero: return fzero + if x == finf: return atan_inf(0, prec, rnd) + if x == fninf: return atan_inf(1, prec, rnd) + return fnan + mag = exp + bc + # Essentially infinity + if mag > prec+20: + return atan_inf(sign, prec, rnd) + # Essentially ~ x + if -mag > prec+20: + return mpf_perturb(x, 1-sign, prec, rnd) + wp = prec + 30 + abs(mag) + # For large x, use atan(x) = pi/2 - atan(1/x) + if mag >= 2: + x = mpf_rdiv_int(1, x, wp) + reciprocal = True + else: + reciprocal = False + t = to_fixed(x, wp) + if sign: + t = -t + if wp < ATAN_TAYLOR_PREC: + a = atan_taylor(t, wp) + else: + a = atan_newton(t, wp) + if reciprocal: + a = ((pi_fixed(wp)>>1)+1) - a + if sign: + a = -a + return from_man_exp(a, -wp, prec, rnd) + +# TODO: cleanup the special cases +def mpf_atan2(y, x, prec, rnd=round_fast): + xsign, xman, xexp, xbc = x + ysign, yman, yexp, ybc = y + if not yman: + if y == fzero and x != fnan: + if mpf_sign(x) >= 0: + return fzero + return mpf_pi(prec, rnd) + if y in (finf, fninf): + if x in (finf, fninf): + return fnan + # pi/2 + if y == finf: + return mpf_shift(mpf_pi(prec, rnd), -1) + # -pi/2 + return mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + return fnan + if ysign: + return mpf_neg(mpf_atan2(mpf_neg(y), x, prec, negative_rnd[rnd])) + if not xman: + if x == fnan: + return fnan + if x == finf: + return fzero + if x == fninf: + return mpf_pi(prec, rnd) + if y == fzero: + return fzero + return mpf_shift(mpf_pi(prec, rnd), -1) + tquo = mpf_atan(mpf_div(y, x, prec+4), prec+4) + if xsign: + return mpf_add(mpf_pi(prec+4), tquo, prec, rnd) + else: + return mpf_pos(tquo, prec, rnd) + +def mpf_asin(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if bc+exp > 0 and x not in (fone, fnone): + raise ComplexResult("asin(x) is real only for -1 <= x <= 1") + # asin(x) = 2*atan(x/(1+sqrt(1-x**2))) + wp = prec + 15 + a = mpf_mul(x, x) + b = mpf_add(fone, mpf_sqrt(mpf_sub(fone, a, wp), wp), wp) + c = mpf_div(x, b, wp) + return mpf_shift(mpf_atan(c, prec, rnd), 1) + +def mpf_acos(x, prec, rnd=round_fast): + # acos(x) = 2*atan(sqrt(1-x**2)/(1+x)) + sign, man, exp, bc = x + if bc + exp > 0: + if x not in (fone, fnone): + raise ComplexResult("acos(x) is real only for -1 <= x <= 1") + if x == fnone: + return mpf_pi(prec, rnd) + wp = prec + 15 + a = mpf_mul(x, x) + b = mpf_sqrt(mpf_sub(fone, a, wp), wp) + c = mpf_div(b, mpf_add(fone, x, wp), wp) + return mpf_shift(mpf_atan(c, prec, rnd), 1) + +def mpf_asinh(x, prec, rnd=round_fast): + wp = prec + 20 + sign, man, exp, bc = x + mag = exp+bc + if mag < -8: + if mag < -wp: + return mpf_perturb(x, 1-sign, prec, rnd) + wp += (-mag) + # asinh(x) = log(x+sqrt(x**2+1)) + # use reflection symmetry to avoid cancellation + q = mpf_sqrt(mpf_add(mpf_mul(x, x), fone, wp), wp) + q = mpf_add(mpf_abs(x), q, wp) + if sign: + return mpf_neg(mpf_log(q, prec, negative_rnd[rnd])) + else: + return mpf_log(q, prec, rnd) + +def mpf_acosh(x, prec, rnd=round_fast): + # acosh(x) = log(x+sqrt(x**2-1)) + wp = prec + 15 + if mpf_cmp(x, fone) == -1: + raise ComplexResult("acosh(x) is real only for x >= 1") + q = mpf_sqrt(mpf_add(mpf_mul(x,x), fnone, wp), wp) + return mpf_log(mpf_add(x, q, wp), prec, rnd) + +def mpf_atanh(x, prec, rnd=round_fast): + # atanh(x) = log((1+x)/(1-x))/2 + sign, man, exp, bc = x + if (not man) and exp: + if x in (fzero, fnan): + return x + raise ComplexResult("atanh(x) is real only for -1 <= x <= 1") + mag = bc + exp + if mag > 0: + if mag == 1 and man == 1: + return [finf, fninf][sign] + raise ComplexResult("atanh(x) is real only for -1 <= x <= 1") + wp = prec + 15 + if mag < -8: + if mag < -wp: + return mpf_perturb(x, sign, prec, rnd) + wp += (-mag) + a = mpf_add(x, fone, wp) + b = mpf_sub(fone, x, wp) + return mpf_shift(mpf_log(mpf_div(a, b, wp), prec, rnd), -1) + +def mpf_fibonacci(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if not man: + if x == fninf: + return fnan + return x + # F(2^n) ~= 2^(2^n) + size = abs(exp+bc) + if exp >= 0: + # Exact + if size < 10 or size <= bitcount(prec): + return from_int(ifib(to_int(x)), prec, rnd) + # Use the modified Binet formula + wp = prec + size + 20 + a = mpf_phi(wp) + b = mpf_add(mpf_shift(a, 1), fnone, wp) + u = mpf_pow(a, x, wp) + v = mpf_cos_pi(x, wp) + v = mpf_div(v, u, wp) + u = mpf_sub(u, v, wp) + u = mpf_div(u, b, prec, rnd) + return u + + +#------------------------------------------------------------------------------- +# Exponential-type functions +#------------------------------------------------------------------------------- + +def exponential_series(x, prec, type=0): + """ + Taylor series for cosh/sinh or cos/sin. + + type = 0 -- returns exp(x) (slightly faster than cosh+sinh) + type = 1 -- returns (cosh(x), sinh(x)) + type = 2 -- returns (cos(x), sin(x)) + """ + if x < 0: + x = -x + sign = 1 + else: + sign = 0 + r = int(0.5*prec**0.5) + xmag = bitcount(x) - prec + r = max(0, xmag + r) + extra = 10 + 2*max(r,-xmag) + wp = prec + extra + x <<= (extra - r) + one = MPZ_ONE << wp + alt = (type == 2) + if prec < EXP_SERIES_U_CUTOFF: + x2 = a = (x*x) >> wp + x4 = (x2*x2) >> wp + s0 = s1 = MPZ_ZERO + k = 2 + while a: + a //= (k-1)*k; s0 += a; k += 2 + a //= (k-1)*k; s1 += a; k += 2 + a = (a*x4) >> wp + s1 = (x2*s1) >> wp + if alt: + c = s1 - s0 + one + else: + c = s1 + s0 + one + else: + u = int(0.3*prec**0.35) + x2 = a = (x*x) >> wp + xpowers = [one, x2] + for i in xrange(1, u): + xpowers.append((xpowers[-1]*x2)>>wp) + sums = [MPZ_ZERO] * u + k = 2 + while a: + for i in xrange(u): + a //= (k-1)*k + if alt and k & 2: sums[i] -= a + else: sums[i] += a + k += 2 + a = (a*xpowers[-1]) >> wp + for i in xrange(1, u): + sums[i] = (sums[i]*xpowers[i]) >> wp + c = sum(sums) + one + if type == 0: + s = isqrt_fast(c*c - (one<> wp + return v >> extra + else: + # Repeatedly apply the double-angle formula + # cosh(2*x) = 2*cosh(x)^2 - 1 + # cos(2*x) = 2*cos(x)^2 - 1 + pshift = wp-1 + for i in xrange(r): + c = ((c*c) >> pshift) - one + # With the abs, this is the same for sinh and sin + s = isqrt_fast(abs((one<>extra), (s>>extra) + +def exp_basecase(x, prec): + """ + Compute exp(x) as a fixed-point number. Works for any x, + but for speed should have |x| < 1. For an arbitrary number, + use exp(x) = exp(x-m*log(2)) * 2^m where m = floor(x/log(2)). + """ + if prec > EXP_COSH_CUTOFF: + return exponential_series(x, prec, 0) + r = int(prec**0.5) + prec += r + s0 = s1 = (MPZ_ONE << prec) + k = 2 + a = x2 = (x*x) >> prec + while a: + a //= k; s0 += a; k += 1 + a //= k; s1 += a; k += 1 + a = (a*x2) >> prec + s1 = (s1*x) >> prec + s = s0 + s1 + u = r + while r: + s = (s*s) >> prec + r -= 1 + return s >> u + +def exp_expneg_basecase(x, prec): + """ + Computation of exp(x), exp(-x) + """ + if prec > EXP_COSH_CUTOFF: + cosh, sinh = exponential_series(x, prec, 1) + return cosh+sinh, cosh-sinh + a = exp_basecase(x, prec) + b = (MPZ_ONE << (prec+prec)) // a + return a, b + +def cos_sin_basecase(x, prec): + """ + Compute cos(x), sin(x) as fixed-point numbers, assuming x + in [0, pi/2). For an arbitrary number, use x' = x - m*(pi/2) + where m = floor(x/(pi/2)) along with quarter-period symmetries. + """ + if prec > COS_SIN_CACHE_PREC: + return exponential_series(x, prec, 2) + precs = prec - COS_SIN_CACHE_STEP + t = x >> precs + n = int(t) + if n not in cos_sin_cache: + w = t<<(10+COS_SIN_CACHE_PREC-COS_SIN_CACHE_STEP) + cos_t, sin_t = exponential_series(w, 10+COS_SIN_CACHE_PREC, 2) + cos_sin_cache[n] = (cos_t>>10), (sin_t>>10) + cos_t, sin_t = cos_sin_cache[n] + offset = COS_SIN_CACHE_PREC - prec + cos_t >>= offset + sin_t >>= offset + x -= t << precs + cos = MPZ_ONE << prec + sin = x + k = 2 + a = -((x*x) >> prec) + while a: + a //= k; cos += a; k += 1; a = (a*x) >> prec + a //= k; sin += a; k += 1; a = -((a*x) >> prec) + return ((cos*cos_t-sin*sin_t) >> prec), ((sin*cos_t+cos*sin_t) >> prec) + +def mpf_exp(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if man: + mag = bc + exp + wp = prec + 14 + if sign: + man = -man + # TODO: the best cutoff depends on both x and the precision. + if prec > 600 and exp >= 0: + # Need about log2(exp(n)) ~= 1.45*mag extra precision + e = mpf_e(wp+int(1.45*mag)) + return mpf_pow_int(e, man<= 2 + if mag > 1: + # For large arguments: exp(2^mag*(1+eps)) = + # exp(2^mag)*exp(2^mag*eps) = exp(2^mag)*(1 + 2^mag*eps + ...) + # so about mag extra bits is required. + wpmod = wp + mag + offset = exp + wpmod + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + lg2 = ln2_fixed(wpmod) + n, t = divmod(t, lg2) + n = int(n) + t >>= mag + else: + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + man = exp_basecase(t, wp) + return from_man_exp(man, n-wp, prec, rnd) + if not exp: + return fone + if x == fninf: + return fzero + return x + + +def mpf_cosh_sinh(x, prec, rnd=round_fast, tanh=0): + """Simultaneously compute (cosh(x), sinh(x)) for real x""" + sign, man, exp, bc = x + if (not man) and exp: + if tanh: + if x == finf: return fone + if x == fninf: return fnone + return fnan + if x == finf: return (finf, finf) + if x == fninf: return (finf, fninf) + return fnan, fnan + mag = exp+bc + wp = prec+14 + if mag < -4: + # Extremely close to 0, sinh(x) ~= x and cosh(x) ~= 1 + if mag < -wp: + if tanh: + return mpf_perturb(x, 1-sign, prec, rnd) + cosh = mpf_perturb(fone, 0, prec, rnd) + sinh = mpf_perturb(x, sign, prec, rnd) + return cosh, sinh + # Fix for cancellation when computing sinh + wp += (-mag) + # Does exp(-2*x) vanish? + if mag > 10: + if 3*(1<<(mag-1)) > wp: + # XXX: rounding + if tanh: + return mpf_perturb([fone,fnone][sign], 1-sign, prec, rnd) + c = s = mpf_shift(mpf_exp(mpf_abs(x), prec, rnd), -1) + if sign: + s = mpf_neg(s) + return c, s + # |x| > 1 + if mag > 1: + wpmod = wp + mag + offset = exp + wpmod + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + lg2 = ln2_fixed(wpmod) + n, t = divmod(t, lg2) + n = int(n) + t >>= mag + else: + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + a, b = exp_expneg_basecase(t, wp) + # TODO: optimize division precision + cosh = a + (b>>(2*n)) + sinh = a - (b>>(2*n)) + if sign: + sinh = -sinh + if tanh: + man = (sinh << wp) // cosh + return from_man_exp(man, -wp, prec, rnd) + else: + cosh = from_man_exp(cosh, n-wp-1, prec, rnd) + sinh = from_man_exp(sinh, n-wp-1, prec, rnd) + return cosh, sinh + + +def mod_pi2(man, exp, mag, wp): + # Reduce to standard interval + if mag > 0: + i = 0 + while 1: + cancellation_prec = 20 << i + wpmod = wp + mag + cancellation_prec + pi2 = pi_fixed(wpmod-1) + pi4 = pi2 >> 1 + offset = wpmod + exp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n, y = divmod(t, pi2) + if y > pi4: + small = pi2 - y + else: + small = y + if small >> (wp+mag-10): + n = int(n) + t = y >> mag + wp = wpmod - mag + break + i += 1 + else: + wp += (-mag) + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + n = 0 + return t, n, wp + + +def mpf_cos_sin(x, prec, rnd=round_fast, which=0, pi=False): + """ + which: + 0 -- return cos(x), sin(x) + 1 -- return cos(x) + 2 -- return sin(x) + 3 -- return tan(x) + + if pi=True, compute for pi*x + """ + sign, man, exp, bc = x + if not man: + if exp: + c, s = fnan, fnan + else: + c, s = fone, fzero + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return s + + mag = bc + exp + wp = prec + 10 + + # Extremely small? + if mag < 0: + if mag < -wp: + if pi: + x = mpf_mul(x, mpf_pi(wp)) + c = mpf_perturb(fone, 1, prec, rnd) + s = mpf_perturb(x, 1-sign, prec, rnd) + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return mpf_perturb(x, sign, prec, rnd) + if pi: + if exp >= -1: + if exp == -1: + c = fzero + s = (fone, fnone)[bool(man & 2) ^ sign] + elif exp == 0: + c, s = (fnone, fzero) + else: + c, s = (fone, fzero) + if which == 0: return c, s + if which == 1: return c + if which == 2: return s + if which == 3: return mpf_div(s, c, prec, rnd) + # Subtract nearest half-integer (= mod by pi/2) + n = ((man >> (-exp-2)) + 1) >> 1 + man = man - (n << (-exp-1)) + mag2 = bitcount(man) + exp + wp = prec + 10 - mag2 + offset = exp + wp + if offset >= 0: + t = man << offset + else: + t = man >> (-offset) + t = (t*pi_fixed(wp)) >> wp + else: + t, n, wp = mod_pi2(man, exp, mag, wp) + c, s = cos_sin_basecase(t, wp) + m = n & 3 + if m == 1: c, s = -s, c + elif m == 2: c, s = -c, -s + elif m == 3: c, s = s, -c + if sign: + s = -s + if which == 0: + c = from_man_exp(c, -wp, prec, rnd) + s = from_man_exp(s, -wp, prec, rnd) + return c, s + if which == 1: + return from_man_exp(c, -wp, prec, rnd) + if which == 2: + return from_man_exp(s, -wp, prec, rnd) + if which == 3: + return from_rational(s, c, prec, rnd) + +def mpf_cos(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 1) +def mpf_sin(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 2) +def mpf_tan(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 3) +def mpf_cos_sin_pi(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 0, 1) +def mpf_cos_pi(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 1, 1) +def mpf_sin_pi(x, prec, rnd=round_fast): return mpf_cos_sin(x, prec, rnd, 2, 1) +def mpf_cosh(x, prec, rnd=round_fast): return mpf_cosh_sinh(x, prec, rnd)[0] +def mpf_sinh(x, prec, rnd=round_fast): return mpf_cosh_sinh(x, prec, rnd)[1] +def mpf_tanh(x, prec, rnd=round_fast): return mpf_cosh_sinh(x, prec, rnd, tanh=1) + + +# Low-overhead fixed-point versions + +def cos_sin_fixed(x, prec, pi2=None): + if pi2 is None: + pi2 = pi_fixed(prec-1) + n, t = divmod(x, pi2) + n = int(n) + c, s = cos_sin_basecase(t, prec) + m = n & 3 + if m == 0: return c, s + if m == 1: return -s, c + if m == 2: return -c, -s + if m == 3: return s, -c + +def exp_fixed(x, prec, ln2=None): + if ln2 is None: + ln2 = ln2_fixed(prec) + n, t = divmod(x, ln2) + n = int(n) + v = exp_basecase(t, prec) + if n >= 0: + return v << n + else: + return v >> (-n) + + +if BACKEND == 'sage': + try: + import sage.libs.mpmath.ext_libmp as _lbmp + mpf_sqrt = _lbmp.mpf_sqrt + mpf_exp = _lbmp.mpf_exp + mpf_log = _lbmp.mpf_log + mpf_cos = _lbmp.mpf_cos + mpf_sin = _lbmp.mpf_sin + mpf_pow = _lbmp.mpf_pow + exp_fixed = _lbmp.exp_fixed + cos_sin_fixed = _lbmp.cos_sin_fixed + log_int_fixed = _lbmp.log_int_fixed + except (ImportError, AttributeError): + print("Warning: Sage imports in libelefun failed") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libhyper.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libhyper.py new file mode 100644 index 0000000000000000000000000000000000000000..04f52d59710be77819066aea5c1cf4b0883f72d7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libhyper.py @@ -0,0 +1,1150 @@ +""" +This module implements computation of hypergeometric and related +functions. In particular, it provides code for generic summation +of hypergeometric series. Optimized versions for various special +cases are also provided. +""" + +import operator +import math + +from .backend import MPZ_ZERO, MPZ_ONE, BACKEND, xrange, exec_ + +from .libintmath import gcd + +from .libmpf import (\ + ComplexResult, round_fast, round_nearest, + negative_rnd, bitcount, to_fixed, from_man_exp, from_int, to_int, + from_rational, + fzero, fone, fnone, ftwo, finf, fninf, fnan, + mpf_sign, mpf_add, mpf_abs, mpf_pos, + mpf_cmp, mpf_lt, mpf_le, mpf_gt, mpf_min_max, + mpf_perturb, mpf_neg, mpf_shift, mpf_sub, mpf_mul, mpf_div, + sqrt_fixed, mpf_sqrt, mpf_rdiv_int, mpf_pow_int, + to_rational, +) + +from .libelefun import (\ + mpf_pi, mpf_exp, mpf_log, pi_fixed, mpf_cos_sin, mpf_cos, mpf_sin, + mpf_sqrt, agm_fixed, +) + +from .libmpc import (\ + mpc_one, mpc_sub, mpc_mul_mpf, mpc_mul, mpc_neg, complex_int_pow, + mpc_div, mpc_add_mpf, mpc_sub_mpf, + mpc_log, mpc_add, mpc_pos, mpc_shift, + mpc_is_infnan, mpc_zero, mpc_sqrt, mpc_abs, + mpc_mpf_div, mpc_square, mpc_exp +) + +from .libintmath import ifac +from .gammazeta import mpf_gamma_int, mpf_euler, euler_fixed + +class NoConvergence(Exception): + pass + + +#-----------------------------------------------------------------------# +# # +# Generic hypergeometric series # +# # +#-----------------------------------------------------------------------# + +""" +TODO: + +1. proper mpq parsing +2. imaginary z special-cased (also: rational, integer?) +3. more clever handling of series that don't converge because of stupid + upwards rounding +4. checking for cancellation + +""" + +def make_hyp_summator(key): + """ + Returns a function that sums a generalized hypergeometric series, + for given parameter types (integer, rational, real, complex). + + """ + p, q, param_types, ztype = key + + pstring = "".join(param_types) + fname = "hypsum_%i_%i_%s_%s_%s" % (p, q, pstring[:p], pstring[p:], ztype) + #print "generating hypsum", fname + + have_complex_param = 'C' in param_types + have_complex_arg = ztype == 'C' + have_complex = have_complex_param or have_complex_arg + + source = [] + add = source.append + + aint = [] + arat = [] + bint = [] + brat = [] + areal = [] + breal = [] + acomplex = [] + bcomplex = [] + + #add("wp = prec + 40") + add("MAX = kwargs.get('maxterms', wp*100)") + add("HIGH = MPZ_ONE<= 0:") + add(" ZRE = xm << offset") + add("else:") + add(" ZRE = xm >> (-offset)") + if have_complex_arg: + add("offset = ye + wp") + add("if offset >= 0:") + add(" ZIM = ym << offset") + add("else:") + add(" ZIM = ym >> (-offset)") + + for i, flag in enumerate(param_types): + W = ["A", "B"][i >= p] + if flag == 'Z': + ([aint,bint][i >= p]).append(i) + add("%sINT_%i = coeffs[%i]" % (W, i, i)) + elif flag == 'Q': + ([arat,brat][i >= p]).append(i) + add("%sP_%i, %sQ_%i = coeffs[%i]._mpq_" % (W, i, W, i, i)) + elif flag == 'R': + ([areal,breal][i >= p]).append(i) + add("xsign, xm, xe, xbc = coeffs[%i]._mpf_" % i) + add("if xsign: xm = -xm") + add("offset = xe + wp") + add("if offset >= 0:") + add(" %sREAL_%i = xm << offset" % (W, i)) + add("else:") + add(" %sREAL_%i = xm >> (-offset)" % (W, i)) + elif flag == 'C': + ([acomplex,bcomplex][i >= p]).append(i) + add("__re, __im = coeffs[%i]._mpc_" % i) + add("xsign, xm, xe, xbc = __re") + add("if xsign: xm = -xm") + add("ysign, ym, ye, ybc = __im") + add("if ysign: ym = -ym") + + add("offset = xe + wp") + add("if offset >= 0:") + add(" %sCRE_%i = xm << offset" % (W, i)) + add("else:") + add(" %sCRE_%i = xm >> (-offset)" % (W, i)) + add("offset = ye + wp") + add("if offset >= 0:") + add(" %sCIM_%i = ym << offset" % (W, i)) + add("else:") + add(" %sCIM_%i = ym >> (-offset)" % (W, i)) + else: + raise ValueError + + l_areal = len(areal) + l_breal = len(breal) + cancellable_real = min(l_areal, l_breal) + noncancellable_real_num = areal[cancellable_real:] + noncancellable_real_den = breal[cancellable_real:] + + # LOOP + add("for n in xrange(1,10**8):") + + add(" if n in magnitude_check:") + add(" p_mag = bitcount(abs(PRE))") + if have_complex: + add(" p_mag = max(p_mag, bitcount(abs(PIM)))") + add(" magnitude_check[n] = wp-p_mag") + + # Real factors + multiplier = " * ".join(["AINT_#".replace("#", str(i)) for i in aint] + \ + ["AP_#".replace("#", str(i)) for i in arat] + \ + ["BQ_#".replace("#", str(i)) for i in brat]) + + divisor = " * ".join(["BINT_#".replace("#", str(i)) for i in bint] + \ + ["BP_#".replace("#", str(i)) for i in brat] + \ + ["AQ_#".replace("#", str(i)) for i in arat] + ["n"]) + + if multiplier: + add(" mul = " + multiplier) + add(" div = " + divisor) + + # Check for singular terms + add(" if not div:") + if multiplier: + add(" if not mul:") + add(" break") + add(" raise ZeroDivisionError") + + # Update product + if have_complex: + + # TODO: when there are several real parameters and just a few complex + # (maybe just the complex argument), we only need to do about + # half as many ops if we accumulate the real factor in a single real variable + for k in range(cancellable_real): add(" PRE = PRE * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PRE = (PRE * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PRE = (PRE << wp) // BREAL_#".replace("#", str(i))) + for k in range(cancellable_real): add(" PIM = PIM * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PIM = (PIM * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PIM = (PIM << wp) // BREAL_#".replace("#", str(i))) + + if multiplier: + if have_complex_arg: + add(" PRE, PIM = (mul*(PRE*ZRE-PIM*ZIM))//div, (mul*(PIM*ZRE+PRE*ZIM))//div") + add(" PRE >>= wp") + add(" PIM >>= wp") + else: + add(" PRE = ((mul * PRE * ZRE) >> wp) // div") + add(" PIM = ((mul * PIM * ZRE) >> wp) // div") + else: + if have_complex_arg: + add(" PRE, PIM = (PRE*ZRE-PIM*ZIM)//div, (PIM*ZRE+PRE*ZIM)//div") + add(" PRE >>= wp") + add(" PIM >>= wp") + else: + add(" PRE = ((PRE * ZRE) >> wp) // div") + add(" PIM = ((PIM * ZRE) >> wp) // div") + + for i in acomplex: + add(" PRE, PIM = PRE*ACRE_#-PIM*ACIM_#, PIM*ACRE_#+PRE*ACIM_#".replace("#", str(i))) + add(" PRE >>= wp") + add(" PIM >>= wp") + + for i in bcomplex: + add(" mag = BCRE_#*BCRE_#+BCIM_#*BCIM_#".replace("#", str(i))) + add(" re = PRE*BCRE_# + PIM*BCIM_#".replace("#", str(i))) + add(" im = PIM*BCRE_# - PRE*BCIM_#".replace("#", str(i))) + add(" PRE = (re << wp) // mag".replace("#", str(i))) + add(" PIM = (im << wp) // mag".replace("#", str(i))) + + else: + for k in range(cancellable_real): add(" PRE = PRE * AREAL_%i // BREAL_%i" % (areal[k], breal[k])) + for i in noncancellable_real_num: add(" PRE = (PRE * AREAL_#) >> wp".replace("#", str(i))) + for i in noncancellable_real_den: add(" PRE = (PRE << wp) // BREAL_#".replace("#", str(i))) + if multiplier: + add(" PRE = ((PRE * mul * ZRE) >> wp) // div") + else: + add(" PRE = ((PRE * ZRE) >> wp) // div") + + # Add product to sum + if have_complex: + add(" SRE += PRE") + add(" SIM += PIM") + add(" if (HIGH > PRE > LOW) and (HIGH > PIM > LOW):") + add(" break") + else: + add(" SRE += PRE") + add(" if HIGH > PRE > LOW:") + add(" break") + + #add(" from mpmath import nprint, log, ldexp") + #add(" nprint([n, log(abs(PRE),2), ldexp(PRE,-wp)])") + + add(" if n > MAX:") + add(" raise NoConvergence('Hypergeometric series converges too slowly. Try increasing maxterms.')") + + # +1 all parameters for next loop + for i in aint: add(" AINT_# += 1".replace("#", str(i))) + for i in bint: add(" BINT_# += 1".replace("#", str(i))) + for i in arat: add(" AP_# += AQ_#".replace("#", str(i))) + for i in brat: add(" BP_# += BQ_#".replace("#", str(i))) + for i in areal: add(" AREAL_# += one".replace("#", str(i))) + for i in breal: add(" BREAL_# += one".replace("#", str(i))) + for i in acomplex: add(" ACRE_# += one".replace("#", str(i))) + for i in bcomplex: add(" BCRE_# += one".replace("#", str(i))) + + if have_complex: + add("a = from_man_exp(SRE, -wp, prec, 'n')") + add("b = from_man_exp(SIM, -wp, prec, 'n')") + + add("if SRE:") + add(" if SIM:") + add(" magn = max(a[2]+a[3], b[2]+b[3])") + add(" else:") + add(" magn = a[2]+a[3]") + add("elif SIM:") + add(" magn = b[2]+b[3]") + add("else:") + add(" magn = -wp+1") + + add("return (a, b), True, magn") + else: + add("a = from_man_exp(SRE, -wp, prec, 'n')") + + add("if SRE:") + add(" magn = a[2]+a[3]") + add("else:") + add(" magn = -wp+1") + + add("return a, False, magn") + + source = "\n".join((" " + line) for line in source) + source = ("def %s(coeffs, z, prec, wp, epsshift, magnitude_check, **kwargs):\n" % fname) + source + + namespace = {} + + exec_(source, globals(), namespace) + + #print source + return source, namespace[fname] + + +if BACKEND == 'sage': + + def make_hyp_summator(key): + """ + Returns a function that sums a generalized hypergeometric series, + for given parameter types (integer, rational, real, complex). + """ + from sage.libs.mpmath.ext_main import hypsum_internal + p, q, param_types, ztype = key + def _hypsum(coeffs, z, prec, wp, epsshift, magnitude_check, **kwargs): + return hypsum_internal(p, q, param_types, ztype, coeffs, z, + prec, wp, epsshift, magnitude_check, kwargs) + + return "(none)", _hypsum + + +#-----------------------------------------------------------------------# +# # +# Error functions # +# # +#-----------------------------------------------------------------------# + +# TODO: mpf_erf should call mpf_erfc when appropriate (currently +# only the converse delegation is implemented) + +def mpf_erf(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if not man: + if x == fzero: return fzero + if x == finf: return fone + if x== fninf: return fnone + return fnan + size = exp + bc + lg = math.log + # The approximation erf(x) = 1 is accurate to > x^2 * log(e,2) bits + if size > 3 and 2*(size-1) + 0.528766 > lg(prec,2): + if sign: + return mpf_perturb(fnone, 0, prec, rnd) + else: + return mpf_perturb(fone, 1, prec, rnd) + # erf(x) ~ 2*x/sqrt(pi) close to 0 + if size < -prec: + # 2*x + x = mpf_shift(x,1) + c = mpf_sqrt(mpf_pi(prec+20), prec+20) + # TODO: interval rounding + return mpf_div(x, c, prec, rnd) + wp = prec + abs(size) + 25 + # Taylor series for erf, fixed-point summation + t = abs(to_fixed(x, wp)) + t2 = (t*t) >> wp + s, term, k = t, 12345, 1 + while term: + t = ((t * t2) >> wp) // k + term = t // (2*k+1) + if k & 1: + s -= term + else: + s += term + k += 1 + s = (s << (wp+1)) // sqrt_fixed(pi_fixed(wp), wp) + if sign: + s = -s + return from_man_exp(s, -wp, prec, rnd) + +# If possible, we use the asymptotic series for erfc. +# This is an alternating divergent asymptotic series, so +# the error is at most equal to the first omitted term. +# Here we check if the smallest term is small enough +# for a given x and precision +def erfc_check_series(x, prec): + n = to_int(x) + if n**2 * 1.44 > prec: + return True + return False + +def mpf_erfc(x, prec, rnd=round_fast): + sign, man, exp, bc = x + if not man: + if x == fzero: return fone + if x == finf: return fzero + if x == fninf: return ftwo + return fnan + wp = prec + 20 + mag = bc+exp + # Preserve full accuracy when exponent grows huge + wp += max(0, 2*mag) + regular_erf = sign or mag < 2 + if regular_erf or not erfc_check_series(x, wp): + if regular_erf: + return mpf_sub(fone, mpf_erf(x, prec+10, negative_rnd[rnd]), prec, rnd) + # 1-erf(x) ~ exp(-x^2), increase prec to deal with cancellation + n = to_int(x)+1 + return mpf_sub(fone, mpf_erf(x, prec + int(n**2*1.44) + 10), prec, rnd) + s = term = MPZ_ONE << wp + term_prev = 0 + t = (2 * to_fixed(x, wp) ** 2) >> wp + k = 1 + while 1: + term = ((term * (2*k - 1)) << wp) // t + if k > 4 and term > term_prev or not term: + break + if k & 1: + s -= term + else: + s += term + term_prev = term + #print k, to_str(from_man_exp(term, -wp, 50), 10) + k += 1 + s = (s << wp) // sqrt_fixed(pi_fixed(wp), wp) + s = from_man_exp(s, -wp, wp) + z = mpf_exp(mpf_neg(mpf_mul(x,x,wp),wp),wp) + y = mpf_div(mpf_mul(z, s, wp), x, prec, rnd) + return y + + +#-----------------------------------------------------------------------# +# # +# Exponential integrals # +# # +#-----------------------------------------------------------------------# + +def ei_taylor(x, prec): + s = t = x + k = 2 + while t: + t = ((t*x) >> prec) // k + s += t // k + k += 1 + return s + +def complex_ei_taylor(zre, zim, prec): + _abs = abs + sre = tre = zre + sim = tim = zim + k = 2 + while _abs(tre) + _abs(tim) > 5: + tre, tim = ((tre*zre-tim*zim)//k)>>prec, ((tre*zim+tim*zre)//k)>>prec + sre += tre // k + sim += tim // k + k += 1 + return sre, sim + +def ei_asymptotic(x, prec): + one = MPZ_ONE << prec + x = t = ((one << prec) // x) + s = one + x + k = 2 + while t: + t = (k*t*x) >> prec + s += t + k += 1 + return s + +def complex_ei_asymptotic(zre, zim, prec): + _abs = abs + one = MPZ_ONE << prec + M = (zim*zim + zre*zre) >> prec + # 1 / z + xre = tre = (zre << prec) // M + xim = tim = ((-zim) << prec) // M + sre = one + xre + sim = xim + k = 2 + while _abs(tre) + _abs(tim) > 1000: + #print tre, tim + tre, tim = ((tre*xre-tim*xim)*k)>>prec, ((tre*xim+tim*xre)*k)>>prec + sre += tre + sim += tim + k += 1 + if k > prec: + raise NoConvergence + return sre, sim + +def mpf_ei(x, prec, rnd=round_fast, e1=False): + if e1: + x = mpf_neg(x) + sign, man, exp, bc = x + if e1 and not sign: + if x == fzero: + return finf + raise ComplexResult("E1(x) for x < 0") + if man: + xabs = 0, man, exp, bc + xmag = exp+bc + wp = prec + 20 + can_use_asymp = xmag > wp + if not can_use_asymp: + if exp >= 0: + xabsint = man << exp + else: + xabsint = man >> (-exp) + can_use_asymp = xabsint > int(wp*0.693) + 10 + if can_use_asymp: + if xmag > wp: + v = fone + else: + v = from_man_exp(ei_asymptotic(to_fixed(x, wp), wp), -wp) + v = mpf_mul(v, mpf_exp(x, wp), wp) + v = mpf_div(v, x, prec, rnd) + else: + wp += 2*int(to_int(xabs)) + u = to_fixed(x, wp) + v = ei_taylor(u, wp) + euler_fixed(wp) + t1 = from_man_exp(v,-wp) + t2 = mpf_log(xabs,wp) + v = mpf_add(t1, t2, prec, rnd) + else: + if x == fzero: v = fninf + elif x == finf: v = finf + elif x == fninf: v = fzero + else: v = fnan + if e1: + v = mpf_neg(v) + return v + +def mpc_ei(z, prec, rnd=round_fast, e1=False): + if e1: + z = mpc_neg(z) + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero: + if e1: + x = mpf_neg(mpf_ei(a, prec, rnd)) + if not asign: + y = mpf_neg(mpf_pi(prec, rnd)) + else: + y = fzero + return x, y + else: + return mpf_ei(a, prec, rnd), fzero + if a != fzero: + if not aman or not bman: + return (fnan, fnan) + wp = prec + 40 + amag = aexp+abc + bmag = bexp+bbc + zmag = max(amag, bmag) + can_use_asymp = zmag > wp + if not can_use_asymp: + zabsint = abs(to_int(a)) + abs(to_int(b)) + can_use_asymp = zabsint > int(wp*0.693) + 20 + try: + if can_use_asymp: + if zmag > wp: + v = fone, fzero + else: + zre = to_fixed(a, wp) + zim = to_fixed(b, wp) + vre, vim = complex_ei_asymptotic(zre, zim, wp) + v = from_man_exp(vre, -wp), from_man_exp(vim, -wp) + v = mpc_mul(v, mpc_exp(z, wp), wp) + v = mpc_div(v, z, wp) + if e1: + v = mpc_neg(v, prec, rnd) + else: + x, y = v + if bsign: + v = mpf_pos(x, prec, rnd), mpf_sub(y, mpf_pi(wp), prec, rnd) + else: + v = mpf_pos(x, prec, rnd), mpf_add(y, mpf_pi(wp), prec, rnd) + return v + except NoConvergence: + pass + #wp += 2*max(0,zmag) + wp += 2*int(to_int(mpc_abs(z, 5))) + zre = to_fixed(a, wp) + zim = to_fixed(b, wp) + vre, vim = complex_ei_taylor(zre, zim, wp) + vre += euler_fixed(wp) + v = from_man_exp(vre,-wp), from_man_exp(vim,-wp) + if e1: + u = mpc_log(mpc_neg(z),wp) + else: + u = mpc_log(z,wp) + v = mpc_add(v, u, prec, rnd) + if e1: + v = mpc_neg(v) + return v + +def mpf_e1(x, prec, rnd=round_fast): + return mpf_ei(x, prec, rnd, True) + +def mpc_e1(x, prec, rnd=round_fast): + return mpc_ei(x, prec, rnd, True) + +def mpf_expint(n, x, prec, rnd=round_fast, gamma=False): + """ + E_n(x), n an integer, x real + + With gamma=True, computes Gamma(n,x) (upper incomplete gamma function) + + Returns (real, None) if real, otherwise (real, imag) + The imaginary part is an optional branch cut term + + """ + sign, man, exp, bc = x + if not man: + if gamma: + if x == fzero: + # Actually gamma function pole + if n <= 0: + return finf, None + return mpf_gamma_int(n, prec, rnd), None + if x == finf: + return fzero, None + # TODO: could return finite imaginary value at -inf + return fnan, fnan + else: + if x == fzero: + if n > 1: + return from_rational(1, n-1, prec, rnd), None + else: + return finf, None + if x == finf: + return fzero, None + return fnan, fnan + n_orig = n + if gamma: + n = 1-n + wp = prec + 20 + xmag = exp + bc + # Beware of near-poles + if xmag < -10: + raise NotImplementedError + nmag = bitcount(abs(n)) + have_imag = n > 0 and sign + negx = mpf_neg(x) + # Skip series if direct convergence + if n == 0 or 2*nmag - xmag < -wp: + if gamma: + v = mpf_exp(negx, wp) + re = mpf_mul(v, mpf_pow_int(x, n_orig-1, wp), prec, rnd) + else: + v = mpf_exp(negx, wp) + re = mpf_div(v, x, prec, rnd) + else: + # Finite number of terms, or... + can_use_asymptotic_series = -3*wp < n <= 0 + # ...large enough? + if not can_use_asymptotic_series: + xi = abs(to_int(x)) + m = min(max(1, xi-n), 2*wp) + siz = -n*nmag + (m+n)*bitcount(abs(m+n)) - m*xmag - (144*m//100) + tol = -wp-10 + can_use_asymptotic_series = siz < tol + if can_use_asymptotic_series: + r = ((-MPZ_ONE) << (wp+wp)) // to_fixed(x, wp) + m = n + t = r*m + s = MPZ_ONE << wp + while m and t: + s += t + m += 1 + t = (m*r*t) >> wp + v = mpf_exp(negx, wp) + if gamma: + # ~ exp(-x) * x^(n-1) * (1 + ...) + v = mpf_mul(v, mpf_pow_int(x, n_orig-1, wp), wp) + else: + # ~ exp(-x)/x * (1 + ...) + v = mpf_div(v, x, wp) + re = mpf_mul(v, from_man_exp(s, -wp), prec, rnd) + elif n == 1: + re = mpf_neg(mpf_ei(negx, prec, rnd)) + elif n > 0 and n < 3*wp: + T1 = mpf_neg(mpf_ei(negx, wp)) + if gamma: + if n_orig & 1: + T1 = mpf_neg(T1) + else: + T1 = mpf_mul(T1, mpf_pow_int(negx, n-1, wp), wp) + r = t = to_fixed(x, wp) + facs = [1] * (n-1) + for k in range(1,n-1): + facs[k] = facs[k-1] * k + facs = facs[::-1] + s = facs[0] << wp + for k in range(1, n-1): + if k & 1: + s -= facs[k] * t + else: + s += facs[k] * t + t = (t*r) >> wp + T2 = from_man_exp(s, -wp, wp) + T2 = mpf_mul(T2, mpf_exp(negx, wp)) + if gamma: + T2 = mpf_mul(T2, mpf_pow_int(x, n_orig, wp), wp) + R = mpf_add(T1, T2) + re = mpf_div(R, from_int(ifac(n-1)), prec, rnd) + else: + raise NotImplementedError + if have_imag: + M = from_int(-ifac(n-1)) + if gamma: + im = mpf_div(mpf_pi(wp), M, prec, rnd) + if n_orig & 1: + im = mpf_neg(im) + else: + im = mpf_div(mpf_mul(mpf_pi(wp), mpf_pow_int(negx, n_orig-1, wp), wp), M, prec, rnd) + return re, im + else: + return re, None + +def mpf_ci_si_taylor(x, wp, which=0): + """ + 0 - Ci(x) - (euler+log(x)) + 1 - Si(x) + """ + x = to_fixed(x, wp) + x2 = -(x*x) >> wp + if which == 0: + s, t, k = 0, (MPZ_ONE<>wp + s += t//k + k += 2 + return from_man_exp(s, -wp) + +def mpc_ci_si_taylor(re, im, wp, which=0): + # The following code is only designed for small arguments, + # and not too small arguments (for relative accuracy) + if re[1]: + mag = re[2]+re[3] + elif im[1]: + mag = im[2]+im[3] + if im[1]: + mag = max(mag, im[2]+im[3]) + if mag > 2 or mag < -wp: + raise NotImplementedError + wp += (2-mag) + zre = to_fixed(re, wp) + zim = to_fixed(im, wp) + z2re = (zim*zim-zre*zre)>>wp + z2im = (-2*zre*zim)>>wp + tre = zre + tim = zim + one = MPZ_ONE< 2: + f = k*(k-1) + tre, tim = ((tre*z2re-tim*z2im)//f)>>wp, ((tre*z2im+tim*z2re)//f)>>wp + sre += tre//k + sim += tim//k + k += 2 + return from_man_exp(sre, -wp), from_man_exp(sim, -wp) + +def mpf_ci_si(x, prec, rnd=round_fast, which=2): + """ + Calculation of Ci(x), Si(x) for real x. + + which = 0 -- returns (Ci(x), -) + which = 1 -- returns (Si(x), -) + which = 2 -- returns (Ci(x), Si(x)) + + Note: if x < 0, Ci(x) needs an additional imaginary term, pi*i. + """ + wp = prec + 20 + sign, man, exp, bc = x + ci, si = None, None + if not man: + if x == fzero: + return (fninf, fzero) + if x == fnan: + return (x, x) + ci = fzero + if which != 0: + if x == finf: + si = mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + si = mpf_neg(mpf_shift(mpf_pi(prec, negative_rnd[rnd]), -1)) + return (ci, si) + # For small x: Ci(x) ~ euler + log(x), Si(x) ~ x + mag = exp+bc + if mag < -wp: + if which != 0: + si = mpf_perturb(x, 1-sign, prec, rnd) + if which != 1: + y = mpf_euler(wp) + xabs = mpf_abs(x) + ci = mpf_add(y, mpf_log(xabs, wp), prec, rnd) + return ci, si + # For huge x: Ci(x) ~ sin(x)/x, Si(x) ~ pi/2 + elif mag > wp: + if which != 0: + if sign: + si = mpf_neg(mpf_pi(prec, negative_rnd[rnd])) + else: + si = mpf_pi(prec, rnd) + si = mpf_shift(si, -1) + if which != 1: + ci = mpf_div(mpf_sin(x, wp), x, prec, rnd) + return ci, si + else: + wp += abs(mag) + # Use an asymptotic series? The smallest value of n!/x^n + # occurs for n ~ x, where the magnitude is ~ exp(-x). + asymptotic = mag-1 > math.log(wp, 2) + # Case 1: convergent series near 0 + if not asymptotic: + if which != 0: + si = mpf_pos(mpf_ci_si_taylor(x, wp, 1), prec, rnd) + if which != 1: + ci = mpf_ci_si_taylor(x, wp, 0) + ci = mpf_add(ci, mpf_euler(wp), wp) + ci = mpf_add(ci, mpf_log(mpf_abs(x), wp), prec, rnd) + return ci, si + x = mpf_abs(x) + # Case 2: asymptotic series for x >> 1 + xf = to_fixed(x, wp) + xr = (MPZ_ONE<<(2*wp)) // xf # 1/x + s1 = (MPZ_ONE << wp) + s2 = xr + t = xr + k = 2 + while t: + t = -t + t = (t*xr*k)>>wp + k += 1 + s1 += t + t = (t*xr*k)>>wp + k += 1 + s2 += t + s1 = from_man_exp(s1, -wp) + s2 = from_man_exp(s2, -wp) + s1 = mpf_div(s1, x, wp) + s2 = mpf_div(s2, x, wp) + cos, sin = mpf_cos_sin(x, wp) + # Ci(x) = sin(x)*s1-cos(x)*s2 + # Si(x) = pi/2-cos(x)*s1-sin(x)*s2 + if which != 0: + si = mpf_add(mpf_mul(cos, s1), mpf_mul(sin, s2), wp) + si = mpf_sub(mpf_shift(mpf_pi(wp), -1), si, wp) + if sign: + si = mpf_neg(si) + si = mpf_pos(si, prec, rnd) + if which != 1: + ci = mpf_sub(mpf_mul(sin, s1), mpf_mul(cos, s2), prec, rnd) + return ci, si + +def mpf_ci(x, prec, rnd=round_fast): + if mpf_sign(x) < 0: + raise ComplexResult + return mpf_ci_si(x, prec, rnd, 0)[0] + +def mpf_si(x, prec, rnd=round_fast): + return mpf_ci_si(x, prec, rnd, 1)[1] + +def mpc_ci(z, prec, rnd=round_fast): + re, im = z + if im == fzero: + ci = mpf_ci_si(re, prec, rnd, 0)[0] + if mpf_sign(re) < 0: + return (ci, mpf_pi(prec, rnd)) + return (ci, fzero) + wp = prec + 20 + cre, cim = mpc_ci_si_taylor(re, im, wp, 0) + cre = mpf_add(cre, mpf_euler(wp), wp) + ci = mpc_add((cre, cim), mpc_log(z, wp), prec, rnd) + return ci + +def mpc_si(z, prec, rnd=round_fast): + re, im = z + if im == fzero: + return (mpf_ci_si(re, prec, rnd, 1)[1], fzero) + wp = prec + 20 + z = mpc_ci_si_taylor(re, im, wp, 1) + return mpc_pos(z, prec, rnd) + + +#-----------------------------------------------------------------------# +# # +# Bessel functions # +# # +#-----------------------------------------------------------------------# + +# A Bessel function of the first kind of integer order, J_n(x), is +# given by the power series + +# oo +# ___ k 2 k + n +# \ (-1) / x \ +# J_n(x) = ) ----------- | - | +# /___ k! (k + n)! \ 2 / +# k = 0 + +# Simplifying the quotient between two successive terms gives the +# ratio x^2 / (-4*k*(k+n)). Hence, we only need one full-precision +# multiplication and one division by a small integer per term. +# The complex version is very similar, the only difference being +# that the multiplication is actually 4 multiplies. + +# In the general case, we have +# J_v(x) = (x/2)**v / v! * 0F1(v+1, (-1/4)*z**2) + +# TODO: for extremely large x, we could use an asymptotic +# trigonometric approximation. + +# TODO: recompute at higher precision if the fixed-point mantissa +# is very small + +def mpf_besseljn(n, x, prec, rounding=round_fast): + prec += 50 + negate = n < 0 and n & 1 + mag = x[2]+x[3] + n = abs(n) + wp = prec + 20 + n*bitcount(n) + if mag < 0: + wp -= n * mag + x = to_fixed(x, wp) + x2 = (x**2) >> wp + if not n: + s = t = MPZ_ONE << wp + else: + s = t = (x**n // ifac(n)) >> ((n-1)*wp + n) + k = 1 + while t: + t = ((t * x2) // (-4*k*(k+n))) >> wp + s += t + k += 1 + if negate: + s = -s + return from_man_exp(s, -wp, prec, rounding) + +def mpc_besseljn(n, z, prec, rounding=round_fast): + negate = n < 0 and n & 1 + n = abs(n) + origprec = prec + zre, zim = z + mag = max(zre[2]+zre[3], zim[2]+zim[3]) + prec += 20 + n*bitcount(n) + abs(mag) + if mag < 0: + prec -= n * mag + zre = to_fixed(zre, prec) + zim = to_fixed(zim, prec) + z2re = (zre**2 - zim**2) >> prec + z2im = (zre*zim) >> (prec-1) + if not n: + sre = tre = MPZ_ONE << prec + sim = tim = MPZ_ZERO + else: + re, im = complex_int_pow(zre, zim, n) + sre = tre = (re // ifac(n)) >> ((n-1)*prec + n) + sim = tim = (im // ifac(n)) >> ((n-1)*prec + n) + k = 1 + while abs(tre) + abs(tim) > 3: + p = -4*k*(k+n) + tre, tim = tre*z2re - tim*z2im, tim*z2re + tre*z2im + tre = (tre // p) >> prec + tim = (tim // p) >> prec + sre += tre + sim += tim + k += 1 + if negate: + sre = -sre + sim = -sim + re = from_man_exp(sre, -prec, origprec, rounding) + im = from_man_exp(sim, -prec, origprec, rounding) + return (re, im) + +def mpf_agm(a, b, prec, rnd=round_fast): + """ + Computes the arithmetic-geometric mean agm(a,b) for + nonnegative mpf values a, b. + """ + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if asign or bsign: + raise ComplexResult("agm of a negative number") + # Handle inf, nan or zero in either operand + if not (aman and bman): + if a == fnan or b == fnan: + return fnan + if a == finf: + if b == fzero: + return fnan + return finf + if b == finf: + if a == fzero: + return fnan + return finf + # agm(0,x) = agm(x,0) = 0 + return fzero + wp = prec + 20 + amag = aexp+abc + bmag = bexp+bbc + mag_delta = amag - bmag + # Reduce to roughly the same magnitude using floating-point AGM + abs_mag_delta = abs(mag_delta) + if abs_mag_delta > 10: + while abs_mag_delta > 10: + a, b = mpf_shift(mpf_add(a,b,wp),-1), \ + mpf_sqrt(mpf_mul(a,b,wp),wp) + abs_mag_delta //= 2 + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + amag = aexp+abc + bmag = bexp+bbc + mag_delta = amag - bmag + #print to_float(a), to_float(b) + # Use agm(a,b) = agm(x*a,x*b)/x to obtain a, b ~= 1 + min_mag = min(amag,bmag) + max_mag = max(amag,bmag) + n = 0 + # If too small, we lose precision when going to fixed-point + if min_mag < -8: + n = -min_mag + # If too large, we waste time using fixed-point with large numbers + elif max_mag > 20: + n = -max_mag + if n: + a = mpf_shift(a, n) + b = mpf_shift(b, n) + #print to_float(a), to_float(b) + af = to_fixed(a, wp) + bf = to_fixed(b, wp) + g = agm_fixed(af, bf, wp) + return from_man_exp(g, -wp-n, prec, rnd) + +def mpf_agm1(a, prec, rnd=round_fast): + """ + Computes the arithmetic-geometric mean agm(1,a) for a nonnegative + mpf value a. + """ + return mpf_agm(fone, a, prec, rnd) + +def mpc_agm(a, b, prec, rnd=round_fast): + """ + Complex AGM. + + TODO: + * check that convergence works as intended + * optimize + * select a nonarbitrary branch + """ + if mpc_is_infnan(a) or mpc_is_infnan(b): + return fnan, fnan + if mpc_zero in (a, b): + return fzero, fzero + if mpc_neg(a) == b: + return fzero, fzero + wp = prec+20 + eps = mpf_shift(fone, -wp+10) + while 1: + a1 = mpc_shift(mpc_add(a, b, wp), -1) + b1 = mpc_sqrt(mpc_mul(a, b, wp), wp) + a, b = a1, b1 + size = mpf_min_max([mpc_abs(a,10), mpc_abs(b,10)])[1] + err = mpc_abs(mpc_sub(a, b, 10), 10) + if size == fzero or mpf_lt(err, mpf_mul(eps, size)): + return a + +def mpc_agm1(a, prec, rnd=round_fast): + return mpc_agm(mpc_one, a, prec, rnd) + +def mpf_ellipk(x, prec, rnd=round_fast): + if not x[1]: + if x == fzero: + return mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + return fzero + if x == fnan: + return x + if x == fone: + return finf + # TODO: for |x| << 1/2, one could use fall back to + # pi/2 * hyp2f1_rat((1,2),(1,2),(1,1), x) + wp = prec + 15 + # Use K(x) = pi/2/agm(1,a) where a = sqrt(1-x) + # The sqrt raises ComplexResult if x > 0 + a = mpf_sqrt(mpf_sub(fone, x, wp), wp) + v = mpf_agm1(a, wp) + r = mpf_div(mpf_pi(wp), v, prec, rnd) + return mpf_shift(r, -1) + +def mpc_ellipk(z, prec, rnd=round_fast): + re, im = z + if im == fzero: + if re == finf: + return mpc_zero + if mpf_le(re, fone): + return mpf_ellipk(re, prec, rnd), fzero + wp = prec + 15 + a = mpc_sqrt(mpc_sub(mpc_one, z, wp), wp) + v = mpc_agm1(a, wp) + r = mpc_mpf_div(mpf_pi(wp), v, prec, rnd) + return mpc_shift(r, -1) + +def mpf_ellipe(x, prec, rnd=round_fast): + # http://functions.wolfram.com/EllipticIntegrals/ + # EllipticK/20/01/0001/ + # E = (1-m)*(K'(m)*2*m + K(m)) + sign, man, exp, bc = x + if not man: + if x == fzero: + return mpf_shift(mpf_pi(prec, rnd), -1) + if x == fninf: + return finf + if x == fnan: + return x + if x == finf: + raise ComplexResult + if x == fone: + return fone + wp = prec+20 + mag = exp+bc + if mag < -wp: + return mpf_shift(mpf_pi(prec, rnd), -1) + # Compute a finite difference for K' + p = max(mag, 0) - wp + h = mpf_shift(fone, p) + K = mpf_ellipk(x, 2*wp) + Kh = mpf_ellipk(mpf_sub(x, h), 2*wp) + Kdiff = mpf_shift(mpf_sub(K, Kh), -p) + t = mpf_sub(fone, x) + b = mpf_mul(Kdiff, mpf_shift(x,1), wp) + return mpf_mul(t, mpf_add(K, b), prec, rnd) + +def mpc_ellipe(z, prec, rnd=round_fast): + re, im = z + if im == fzero: + if re == finf: + return (fzero, finf) + if mpf_le(re, fone): + return mpf_ellipe(re, prec, rnd), fzero + wp = prec + 15 + mag = mpc_abs(z, 1) + p = max(mag[2]+mag[3], 0) - wp + h = mpf_shift(fone, p) + K = mpc_ellipk(z, 2*wp) + Kh = mpc_ellipk(mpc_add_mpf(z, h, 2*wp), 2*wp) + Kdiff = mpc_shift(mpc_sub(Kh, K, wp), -p) + t = mpc_sub(mpc_one, z, wp) + b = mpc_mul(Kdiff, mpc_shift(z,1), wp) + return mpc_mul(t, mpc_add(K, b, wp), prec, rnd) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libintmath.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libintmath.py new file mode 100644 index 0000000000000000000000000000000000000000..7880546e135639208d136488408b102ad41682a2 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libintmath.py @@ -0,0 +1,584 @@ +""" +Utility functions for integer math. + +TODO: rename, cleanup, perhaps move the gmpy wrapper code +here from settings.py + +""" + +import math +from bisect import bisect + +from .backend import xrange +from .backend import BACKEND, gmpy, sage, sage_utils, MPZ, MPZ_ONE, MPZ_ZERO + +small_trailing = [0] * 256 +for j in range(1,8): + small_trailing[1<>> giant_steps(50,1000) + [66, 128, 253, 502, 1000] + >>> giant_steps(50,1000,4) + [65, 252, 1000] + + """ + L = [target] + while L[-1] > start*n: + L = L + [L[-1]//n + 2] + return L[::-1] + +def rshift(x, n): + """For an integer x, calculate x >> n with the fastest (floor) + rounding. Unlike the plain Python expression (x >> n), n is + allowed to be negative, in which case a left shift is performed.""" + if n >= 0: return x >> n + else: return x << (-n) + +def lshift(x, n): + """For an integer x, calculate x << n. Unlike the plain Python + expression (x << n), n is allowed to be negative, in which case a + right shift with default (floor) rounding is performed.""" + if n >= 0: return x << n + else: return x >> (-n) + +if BACKEND == 'sage': + import operator + rshift = operator.rshift + lshift = operator.lshift + +def python_trailing(n): + """Count the number of trailing zero bits in abs(n).""" + if not n: + return 0 + low_byte = n & 0xff + if low_byte: + return small_trailing[low_byte] + t = 8 + n >>= 8 + while not n & 0xff: + n >>= 8 + t += 8 + return t + small_trailing[n & 0xff] + +if BACKEND == 'gmpy': + if gmpy.version() >= '2': + def gmpy_trailing(n): + """Count the number of trailing zero bits in abs(n) using gmpy.""" + if n: return MPZ(n).bit_scan1() + else: return 0 + else: + def gmpy_trailing(n): + """Count the number of trailing zero bits in abs(n) using gmpy.""" + if n: return MPZ(n).scan1() + else: return 0 + +# Small powers of 2 +powers = [1<<_ for _ in range(300)] + +def python_bitcount(n): + """Calculate bit size of the nonnegative integer n.""" + bc = bisect(powers, n) + if bc != 300: + return bc + bc = int(math.log(n, 2)) - 4 + return bc + bctable[n>>bc] + +def gmpy_bitcount(n): + """Calculate bit size of the nonnegative integer n.""" + if n: return MPZ(n).numdigits(2) + else: return 0 + +#def sage_bitcount(n): +# if n: return MPZ(n).nbits() +# else: return 0 + +def sage_trailing(n): + return MPZ(n).trailing_zero_bits() + +if BACKEND == 'gmpy': + bitcount = gmpy_bitcount + trailing = gmpy_trailing +elif BACKEND == 'sage': + sage_bitcount = sage_utils.bitcount + bitcount = sage_bitcount + trailing = sage_trailing +else: + bitcount = python_bitcount + trailing = python_trailing + +if BACKEND == 'gmpy' and 'bit_length' in dir(gmpy): + bitcount = gmpy.bit_length + +# Used to avoid slow function calls as far as possible +trailtable = [trailing(n) for n in range(256)] +bctable = [bitcount(n) for n in range(1024)] + +# TODO: speed up for bases 2, 4, 8, 16, ... + +def bin_to_radix(x, xbits, base, bdigits): + """Changes radix of a fixed-point number; i.e., converts + x * 2**xbits to floor(x * 10**bdigits).""" + return x * (MPZ(base)**bdigits) >> xbits + +stddigits = '0123456789abcdefghijklmnopqrstuvwxyz' + +def small_numeral(n, base=10, digits=stddigits): + """Return the string numeral of a positive integer in an arbitrary + base. Most efficient for small input.""" + if base == 10: + return str(n) + digs = [] + while n: + n, digit = divmod(n, base) + digs.append(digits[digit]) + return "".join(digs[::-1]) + +def numeral_python(n, base=10, size=0, digits=stddigits): + """Represent the integer n as a string of digits in the given base. + Recursive division is used to make this function about 3x faster + than Python's str() for converting integers to decimal strings. + + The 'size' parameters specifies the number of digits in n; this + number is only used to determine splitting points and need not be + exact.""" + if n <= 0: + if not n: + return "0" + return "-" + numeral(-n, base, size, digits) + # Fast enough to do directly + if size < 250: + return small_numeral(n, base, digits) + # Divide in half + half = (size // 2) + (size & 1) + A, B = divmod(n, base**half) + ad = numeral(A, base, half, digits) + bd = numeral(B, base, half, digits).rjust(half, "0") + return ad + bd + +def numeral_gmpy(n, base=10, size=0, digits=stddigits): + """Represent the integer n as a string of digits in the given base. + Recursive division is used to make this function about 3x faster + than Python's str() for converting integers to decimal strings. + + The 'size' parameters specifies the number of digits in n; this + number is only used to determine splitting points and need not be + exact.""" + if n < 0: + return "-" + numeral(-n, base, size, digits) + # gmpy.digits() may cause a segmentation fault when trying to convert + # extremely large values to a string. The size limit may need to be + # adjusted on some platforms, but 1500000 works on Windows and Linux. + if size < 1500000: + return gmpy.digits(n, base) + # Divide in half + half = (size // 2) + (size & 1) + A, B = divmod(n, MPZ(base)**half) + ad = numeral(A, base, half, digits) + bd = numeral(B, base, half, digits).rjust(half, "0") + return ad + bd + +if BACKEND == "gmpy": + numeral = numeral_gmpy +else: + numeral = numeral_python + +_1_800 = 1<<800 +_1_600 = 1<<600 +_1_400 = 1<<400 +_1_200 = 1<<200 +_1_100 = 1<<100 +_1_50 = 1<<50 + +def isqrt_small_python(x): + """ + Correctly (floor) rounded integer square root, using + division. Fast up to ~200 digits. + """ + if not x: + return x + if x < _1_800: + # Exact with IEEE double precision arithmetic + if x < _1_50: + return int(x**0.5) + # Initial estimate can be any integer >= the true root; round up + r = int(x**0.5 * 1.00000000000001) + 1 + else: + bc = bitcount(x) + n = bc//2 + r = int((x>>(2*n-100))**0.5+2)<<(n-50) # +2 is to round up + # The following iteration now precisely computes floor(sqrt(x)) + # See e.g. Crandall & Pomerance, "Prime Numbers: A Computational + # Perspective" + while 1: + y = (r+x//r)>>1 + if y >= r: + return r + r = y + +def isqrt_fast_python(x): + """ + Fast approximate integer square root, computed using division-free + Newton iteration for large x. For random integers the result is almost + always correct (floor(sqrt(x))), but is 1 ulp too small with a roughly + 0.1% probability. If x is very close to an exact square, the answer is + 1 ulp wrong with high probability. + + With 0 guard bits, the largest error over a set of 10^5 random + inputs of size 1-10^5 bits was 3 ulp. The use of 10 guard bits + almost certainly guarantees a max 1 ulp error. + """ + # Use direct division-based iteration if sqrt(x) < 2^400 + # Assume floating-point square root accurate to within 1 ulp, then: + # 0 Newton iterations good to 52 bits + # 1 Newton iterations good to 104 bits + # 2 Newton iterations good to 208 bits + # 3 Newton iterations good to 416 bits + if x < _1_800: + y = int(x**0.5) + if x >= _1_100: + y = (y + x//y) >> 1 + if x >= _1_200: + y = (y + x//y) >> 1 + if x >= _1_400: + y = (y + x//y) >> 1 + return y + bc = bitcount(x) + guard_bits = 10 + x <<= 2*guard_bits + bc += 2*guard_bits + bc += (bc&1) + hbc = bc//2 + startprec = min(50, hbc) + # Newton iteration for 1/sqrt(x), with floating-point starting value + r = int(2.0**(2*startprec) * (x >> (bc-2*startprec)) ** -0.5) + pp = startprec + for p in giant_steps(startprec, hbc): + # r**2, scaled from real size 2**(-bc) to 2**p + r2 = (r*r) >> (2*pp - p) + # x*r**2, scaled from real size ~1.0 to 2**p + xr2 = ((x >> (bc-p)) * r2) >> p + # New value of r, scaled from real size 2**(-bc/2) to 2**p + r = (r * ((3<> (pp+1) + pp = p + # (1/sqrt(x))*x = sqrt(x) + return (r*(x>>hbc)) >> (p+guard_bits) + +def sqrtrem_python(x): + """Correctly rounded integer (floor) square root with remainder.""" + # to check cutoff: + # plot(lambda x: timing(isqrt, 2**int(x)), [0,2000]) + if x < _1_600: + y = isqrt_small_python(x) + return y, x - y*y + y = isqrt_fast_python(x) + 1 + rem = x - y*y + # Correct remainder + while rem < 0: + y -= 1 + rem += (1+2*y) + else: + if rem: + while rem > 2*(1+y): + y += 1 + rem -= (1+2*y) + return y, rem + +def isqrt_python(x): + """Integer square root with correct (floor) rounding.""" + return sqrtrem_python(x)[0] + +def sqrt_fixed(x, prec): + return isqrt_fast(x<= '2': + isqrt_small = isqrt_fast = isqrt = gmpy.isqrt + sqrtrem = gmpy.isqrt_rem + else: + isqrt_small = isqrt_fast = isqrt = gmpy.sqrt + sqrtrem = gmpy.sqrtrem +elif BACKEND == 'sage': + isqrt_small = isqrt_fast = isqrt = \ + getattr(sage_utils, "isqrt", lambda n: MPZ(n).isqrt()) + sqrtrem = lambda n: MPZ(n).sqrtrem() +else: + isqrt_small = isqrt_small_python + isqrt_fast = isqrt_fast_python + isqrt = isqrt_python + sqrtrem = sqrtrem_python + + +def ifib(n, _cache={}): + """Computes the nth Fibonacci number as an integer, for + integer n.""" + if n < 0: + return (-1)**(-n+1) * ifib(-n) + if n in _cache: + return _cache[n] + m = n + # Use Dijkstra's logarithmic algorithm + # The following implementation is basically equivalent to + # http://en.literateprograms.org/Fibonacci_numbers_(Scheme) + a, b, p, q = MPZ_ONE, MPZ_ZERO, MPZ_ZERO, MPZ_ONE + while n: + if n & 1: + aq = a*q + a, b = b*q+aq+a*p, b*p+aq + n -= 1 + else: + qq = q*q + p, q = p*p+qq, qq+2*p*q + n >>= 1 + if m < 250: + _cache[m] = b + return b + +MAX_FACTORIAL_CACHE = 1000 + +def ifac(n, memo={0:1, 1:1}): + """Return n factorial (for integers n >= 0 only).""" + f = memo.get(n) + if f: + return f + k = len(memo) + p = memo[k-1] + MAX = MAX_FACTORIAL_CACHE + while k <= n: + p *= k + if k <= MAX: + memo[k] = p + k += 1 + return p + +def ifac2(n, memo_pair=[{0:1}, {1:1}]): + """Return n!! (double factorial), integers n >= 0 only.""" + memo = memo_pair[n&1] + f = memo.get(n) + if f: + return f + k = max(memo) + p = memo[k] + MAX = MAX_FACTORIAL_CACHE + while k < n: + k += 2 + p *= k + if k <= MAX: + memo[k] = p + return p + +if BACKEND == 'gmpy': + ifac = gmpy.fac +elif BACKEND == 'sage': + ifac = lambda n: int(sage.factorial(n)) + ifib = sage.fibonacci + +def list_primes(n): + n = n + 1 + sieve = list(xrange(n)) + sieve[:2] = [0, 0] + for i in xrange(2, int(n**0.5)+1): + if sieve[i]: + for j in xrange(i**2, n, i): + sieve[j] = 0 + return [p for p in sieve if p] + +if BACKEND == 'sage': + # Note: it is *VERY* important for performance that we convert + # the list to Python ints. + def list_primes(n): + return [int(_) for _ in sage.primes(n+1)] + +small_odd_primes = (3,5,7,11,13,17,19,23,29,31,37,41,43,47) +small_odd_primes_set = set(small_odd_primes) + +def isprime(n): + """ + Determines whether n is a prime number. A probabilistic test is + performed if n is very large. No special trick is used for detecting + perfect powers. + + >>> sum(list_primes(100000)) + 454396537 + >>> sum(n*isprime(n) for n in range(100000)) + 454396537 + + """ + n = int(n) + if not n & 1: + return n == 2 + if n < 50: + return n in small_odd_primes_set + for p in small_odd_primes: + if not n % p: + return False + m = n-1 + s = trailing(m) + d = m >> s + def test(a): + x = pow(a,d,n) + if x == 1 or x == m: + return True + for r in xrange(1,s): + x = x**2 % n + if x == m: + return True + return False + # See http://primes.utm.edu/prove/prove2_3.html + if n < 1373653: + witnesses = [2,3] + elif n < 341550071728321: + witnesses = [2,3,5,7,11,13,17] + else: + witnesses = small_odd_primes + for a in witnesses: + if not test(a): + return False + return True + +def moebius(n): + """ + Evaluates the Moebius function which is `mu(n) = (-1)^k` if `n` + is a product of `k` distinct primes and `mu(n) = 0` otherwise. + + TODO: speed up using factorization + """ + n = abs(int(n)) + if n < 2: + return n + factors = [] + for p in xrange(2, n+1): + if not (n % p): + if not (n % p**2): + return 0 + if not sum(p % f for f in factors): + factors.append(p) + return (-1)**len(factors) + +def gcd(*args): + a = 0 + for b in args: + if a: + while b: + a, b = b, a % b + else: + a = b + return a + + +# Comment by Juan Arias de Reyna: +# +# I learn this method to compute EulerE[2n] from van de Lune. +# +# We apply the formula EulerE[2n] = (-1)^n 2**(-2n) sum_{j=0}^n a(2n,2j+1) +# +# where the numbers a(n,j) vanish for j > n+1 or j <= -1 and satisfies +# +# a(0,-1) = a(0,0) = 0; a(0,1)= 1; a(0,2) = a(0,3) = 0 +# +# a(n,j) = a(n-1,j) when n+j is even +# a(n,j) = (j-1) a(n-1,j-1) + (j+1) a(n-1,j+1) when n+j is odd +# +# +# But we can use only one array unidimensional a(j) since to compute +# a(n,j) we only need to know a(n-1,k) where k and j are of different parity +# and we have not to conserve the used values. +# +# We cached up the values of Euler numbers to sufficiently high order. +# +# Important Observation: If we pretend to use the numbers +# EulerE[1], EulerE[2], ... , EulerE[n] +# it is convenient to compute first EulerE[n], since the algorithm +# computes first all +# the previous ones, and keeps them in the CACHE + +MAX_EULER_CACHE = 500 + +def eulernum(m, _cache={0:MPZ_ONE}): + r""" + Computes the Euler numbers `E(n)`, which can be defined as + coefficients of the Taylor expansion of `1/cosh x`: + + .. math :: + + \frac{1}{\cosh x} = \sum_{n=0}^\infty \frac{E_n}{n!} x^n + + Example:: + + >>> [int(eulernum(n)) for n in range(11)] + [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521] + >>> [int(eulernum(n)) for n in range(11)] # test cache + [1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521] + + """ + # for odd m > 1, the Euler numbers are zero + if m & 1: + return MPZ_ZERO + f = _cache.get(m) + if f: + return f + MAX = MAX_EULER_CACHE + n = m + a = [MPZ(_) for _ in [0,0,1,0,0,0]] + for n in range(1, m+1): + for j in range(n+1, -1, -2): + a[j+1] = (j-1)*a[j] + (j+1)*a[j+2] + a.append(0) + suma = 0 + for k in range(n+1, -1, -2): + suma += a[k+1] + if n <= MAX: + _cache[n] = ((-1)**(n//2))*(suma // 2**n) + if n == m: + return ((-1)**(n//2))*suma // 2**n + +def stirling1(n, k): + """ + Stirling number of the first kind. + """ + if n < 0 or k < 0: + raise ValueError + if k >= n: + return MPZ(n == k) + if k < 1: + return MPZ_ZERO + L = [MPZ_ZERO] * (k+1) + L[1] = MPZ_ONE + for m in xrange(2, n+1): + for j in xrange(min(k, m), 0, -1): + L[j] = (m-1) * L[j] + L[j-1] + return (-1)**(n+k) * L[k] + +def stirling2(n, k): + """ + Stirling number of the second kind. + """ + if n < 0 or k < 0: + raise ValueError + if k >= n: + return MPZ(n == k) + if k <= 1: + return MPZ(k == 1) + s = MPZ_ZERO + t = MPZ_ONE + for j in xrange(k+1): + if (k + j) & 1: + s -= t * MPZ(j)**n + else: + s += t * MPZ(j)**n + t = t * (k - j) // (j + 1) + return s // ifac(k) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpc.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpc.py new file mode 100644 index 0000000000000000000000000000000000000000..cc22d0e73674676c8a9249ebc2d48da7f3be8b0d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpc.py @@ -0,0 +1,835 @@ +""" +Low-level functions for complex arithmetic. +""" + +import sys + +from .backend import MPZ, MPZ_ZERO, MPZ_ONE, MPZ_TWO, BACKEND + +from .libmpf import (\ + round_floor, round_ceiling, round_down, round_up, + round_nearest, round_fast, bitcount, + bctable, normalize, normalize1, reciprocal_rnd, rshift, lshift, giant_steps, + negative_rnd, + to_str, to_fixed, from_man_exp, from_float, to_float, from_int, to_int, + fzero, fone, ftwo, fhalf, finf, fninf, fnan, fnone, + mpf_abs, mpf_pos, mpf_neg, mpf_add, mpf_sub, mpf_mul, + mpf_div, mpf_mul_int, mpf_shift, mpf_sqrt, mpf_hypot, + mpf_rdiv_int, mpf_floor, mpf_ceil, mpf_nint, mpf_frac, + mpf_sign, mpf_hash, + ComplexResult +) + +from .libelefun import (\ + mpf_pi, mpf_exp, mpf_log, mpf_cos_sin, mpf_cosh_sinh, mpf_tan, mpf_pow_int, + mpf_log_hypot, + mpf_cos_sin_pi, mpf_phi, + mpf_cos, mpf_sin, mpf_cos_pi, mpf_sin_pi, + mpf_atan, mpf_atan2, mpf_cosh, mpf_sinh, mpf_tanh, + mpf_asin, mpf_acos, mpf_acosh, mpf_nthroot, mpf_fibonacci +) + +# An mpc value is a (real, imag) tuple +mpc_one = fone, fzero +mpc_zero = fzero, fzero +mpc_two = ftwo, fzero +mpc_half = (fhalf, fzero) + +_infs = (finf, fninf) +_infs_nan = (finf, fninf, fnan) + +def mpc_is_inf(z): + """Check if either real or imaginary part is infinite""" + re, im = z + if re in _infs: return True + if im in _infs: return True + return False + +def mpc_is_infnan(z): + """Check if either real or imaginary part is infinite or nan""" + re, im = z + if re in _infs_nan: return True + if im in _infs_nan: return True + return False + +def mpc_to_str(z, dps, **kwargs): + re, im = z + rs = to_str(re, dps) + if im[0]: + return rs + " - " + to_str(mpf_neg(im), dps, **kwargs) + "j" + else: + return rs + " + " + to_str(im, dps, **kwargs) + "j" + +def mpc_to_complex(z, strict=False, rnd=round_fast): + re, im = z + return complex(to_float(re, strict, rnd), to_float(im, strict, rnd)) + +def mpc_hash(z): + if sys.version_info >= (3, 2): + re, im = z + h = mpf_hash(re) + sys.hash_info.imag * mpf_hash(im) + # Need to reduce either module 2^32 or 2^64 + h = h % (2**sys.hash_info.width) + return int(h) + else: + try: + return hash(mpc_to_complex(z, strict=True)) + except OverflowError: + return hash(z) + +def mpc_conjugate(z, prec, rnd=round_fast): + re, im = z + return re, mpf_neg(im, prec, rnd) + +def mpc_is_nonzero(z): + return z != mpc_zero + +def mpc_add(z, w, prec, rnd=round_fast): + a, b = z + c, d = w + return mpf_add(a, c, prec, rnd), mpf_add(b, d, prec, rnd) + +def mpc_add_mpf(z, x, prec, rnd=round_fast): + a, b = z + return mpf_add(a, x, prec, rnd), b + +def mpc_sub(z, w, prec=0, rnd=round_fast): + a, b = z + c, d = w + return mpf_sub(a, c, prec, rnd), mpf_sub(b, d, prec, rnd) + +def mpc_sub_mpf(z, p, prec=0, rnd=round_fast): + a, b = z + return mpf_sub(a, p, prec, rnd), b + +def mpc_pos(z, prec, rnd=round_fast): + a, b = z + return mpf_pos(a, prec, rnd), mpf_pos(b, prec, rnd) + +def mpc_neg(z, prec=None, rnd=round_fast): + a, b = z + return mpf_neg(a, prec, rnd), mpf_neg(b, prec, rnd) + +def mpc_shift(z, n): + a, b = z + return mpf_shift(a, n), mpf_shift(b, n) + +def mpc_abs(z, prec, rnd=round_fast): + """Absolute value of a complex number, |a+bi|. + Returns an mpf value.""" + a, b = z + return mpf_hypot(a, b, prec, rnd) + +def mpc_arg(z, prec, rnd=round_fast): + """Argument of a complex number. Returns an mpf value.""" + a, b = z + return mpf_atan2(b, a, prec, rnd) + +def mpc_floor(z, prec, rnd=round_fast): + a, b = z + return mpf_floor(a, prec, rnd), mpf_floor(b, prec, rnd) + +def mpc_ceil(z, prec, rnd=round_fast): + a, b = z + return mpf_ceil(a, prec, rnd), mpf_ceil(b, prec, rnd) + +def mpc_nint(z, prec, rnd=round_fast): + a, b = z + return mpf_nint(a, prec, rnd), mpf_nint(b, prec, rnd) + +def mpc_frac(z, prec, rnd=round_fast): + a, b = z + return mpf_frac(a, prec, rnd), mpf_frac(b, prec, rnd) + + +def mpc_mul(z, w, prec, rnd=round_fast): + """ + Complex multiplication. + + Returns the real and imaginary part of (a+bi)*(c+di), rounded to + the specified precision. The rounding mode applies to the real and + imaginary parts separately. + """ + a, b = z + c, d = w + p = mpf_mul(a, c) + q = mpf_mul(b, d) + r = mpf_mul(a, d) + s = mpf_mul(b, c) + re = mpf_sub(p, q, prec, rnd) + im = mpf_add(r, s, prec, rnd) + return re, im + +def mpc_square(z, prec, rnd=round_fast): + # (a+b*I)**2 == a**2 - b**2 + 2*I*a*b + a, b = z + p = mpf_mul(a,a) + q = mpf_mul(b,b) + r = mpf_mul(a,b, prec, rnd) + re = mpf_sub(p, q, prec, rnd) + im = mpf_shift(r, 1) + return re, im + +def mpc_mul_mpf(z, p, prec, rnd=round_fast): + a, b = z + re = mpf_mul(a, p, prec, rnd) + im = mpf_mul(b, p, prec, rnd) + return re, im + +def mpc_mul_imag_mpf(z, x, prec, rnd=round_fast): + """ + Multiply the mpc value z by I*x where x is an mpf value. + """ + a, b = z + re = mpf_neg(mpf_mul(b, x, prec, rnd)) + im = mpf_mul(a, x, prec, rnd) + return re, im + +def mpc_mul_int(z, n, prec, rnd=round_fast): + a, b = z + re = mpf_mul_int(a, n, prec, rnd) + im = mpf_mul_int(b, n, prec, rnd) + return re, im + +def mpc_div(z, w, prec, rnd=round_fast): + a, b = z + c, d = w + wp = prec + 10 + # mag = c*c + d*d + mag = mpf_add(mpf_mul(c, c), mpf_mul(d, d), wp) + # (a*c+b*d)/mag, (b*c-a*d)/mag + t = mpf_add(mpf_mul(a,c), mpf_mul(b,d), wp) + u = mpf_sub(mpf_mul(b,c), mpf_mul(a,d), wp) + return mpf_div(t,mag,prec,rnd), mpf_div(u,mag,prec,rnd) + +def mpc_div_mpf(z, p, prec, rnd=round_fast): + """Calculate z/p where p is real""" + a, b = z + re = mpf_div(a, p, prec, rnd) + im = mpf_div(b, p, prec, rnd) + return re, im + +def mpc_reciprocal(z, prec, rnd=round_fast): + """Calculate 1/z efficiently""" + a, b = z + m = mpf_add(mpf_mul(a,a),mpf_mul(b,b),prec+10) + re = mpf_div(a, m, prec, rnd) + im = mpf_neg(mpf_div(b, m, prec, rnd)) + return re, im + +def mpc_mpf_div(p, z, prec, rnd=round_fast): + """Calculate p/z where p is real efficiently""" + a, b = z + m = mpf_add(mpf_mul(a,a),mpf_mul(b,b), prec+10) + re = mpf_div(mpf_mul(a,p), m, prec, rnd) + im = mpf_div(mpf_neg(mpf_mul(b,p)), m, prec, rnd) + return re, im + +def complex_int_pow(a, b, n): + """Complex integer power: computes (a+b*I)**n exactly for + nonnegative n (a and b must be Python ints).""" + wre = 1 + wim = 0 + while n: + if n & 1: + wre, wim = wre*a - wim*b, wim*a + wre*b + n -= 1 + a, b = a*a - b*b, 2*a*b + n //= 2 + return wre, wim + +def mpc_pow(z, w, prec, rnd=round_fast): + if w[1] == fzero: + return mpc_pow_mpf(z, w[0], prec, rnd) + return mpc_exp(mpc_mul(mpc_log(z, prec+10), w, prec+10), prec, rnd) + +def mpc_pow_mpf(z, p, prec, rnd=round_fast): + psign, pman, pexp, pbc = p + if pexp >= 0: + return mpc_pow_int(z, (-1)**psign * (pman< 0: + aman <<= de + aexp = bexp + else: + bman <<= (-de) + bexp = aexp + re, im = complex_int_pow(aman, bman, n) + re = from_man_exp(re, int(n*aexp), prec, rnd) + im = from_man_exp(im, int(n*bexp), prec, rnd) + return re, im + return mpc_exp(mpc_mul_int(mpc_log(z, prec+10), n, prec+10), prec, rnd) + +def mpc_sqrt(z, prec, rnd=round_fast): + """Complex square root (principal branch). + + We have sqrt(a+bi) = sqrt((r+a)/2) + b/sqrt(2*(r+a))*i where + r = abs(a+bi), when a+bi is not a negative real number.""" + a, b = z + if b == fzero: + if a == fzero: + return (a, b) + # When a+bi is a negative real number, we get a real sqrt times i + if a[0]: + im = mpf_sqrt(mpf_neg(a), prec, rnd) + return (fzero, im) + else: + re = mpf_sqrt(a, prec, rnd) + return (re, fzero) + wp = prec+20 + if not a[0]: # case a positive + t = mpf_add(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) + a + u = mpf_shift(t, -1) # u = t/2 + re = mpf_sqrt(u, prec, rnd) # re = sqrt(u) + v = mpf_shift(t, 1) # v = 2*t + w = mpf_sqrt(v, wp) # w = sqrt(v) + im = mpf_div(b, w, prec, rnd) # im = b / w + else: # case a negative + t = mpf_sub(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) - a + u = mpf_shift(t, -1) # u = t/2 + im = mpf_sqrt(u, prec, rnd) # im = sqrt(u) + v = mpf_shift(t, 1) # v = 2*t + w = mpf_sqrt(v, wp) # w = sqrt(v) + re = mpf_div(b, w, prec, rnd) # re = b/w + if b[0]: + re = mpf_neg(re) + im = mpf_neg(im) + return re, im + +def mpc_nthroot_fixed(a, b, n, prec): + # a, b signed integers at fixed precision prec + start = 50 + a1 = int(rshift(a, prec - n*start)) + b1 = int(rshift(b, prec - n*start)) + try: + r = (a1 + 1j * b1)**(1.0/n) + re = r.real + im = r.imag + re = MPZ(int(re)) + im = MPZ(int(im)) + except OverflowError: + a1 = from_int(a1, start) + b1 = from_int(b1, start) + fn = from_int(n) + nth = mpf_rdiv_int(1, fn, start) + re, im = mpc_pow((a1, b1), (nth, fzero), start) + re = to_int(re) + im = to_int(im) + extra = 10 + prevp = start + extra1 = n + for p in giant_steps(start, prec+extra): + # this is slow for large n, unlike int_pow_fixed + re2, im2 = complex_int_pow(re, im, n-1) + re2 = rshift(re2, (n-1)*prevp - p - extra1) + im2 = rshift(im2, (n-1)*prevp - p - extra1) + r4 = (re2*re2 + im2*im2) >> (p + extra1) + ap = rshift(a, prec - p) + bp = rshift(b, prec - p) + rec = (ap * re2 + bp * im2) >> p + imc = (-ap * im2 + bp * re2) >> p + reb = (rec << p) // r4 + imb = (imc << p) // r4 + re = (reb + (n-1)*lshift(re, p-prevp))//n + im = (imb + (n-1)*lshift(im, p-prevp))//n + prevp = p + return re, im + +def mpc_nthroot(z, n, prec, rnd=round_fast): + """ + Complex n-th root. + + Use Newton method as in the real case when it is faster, + otherwise use z**(1/n) + """ + a, b = z + if a[0] == 0 and b == fzero: + re = mpf_nthroot(a, n, prec, rnd) + return (re, fzero) + if n < 2: + if n == 0: + return mpc_one + if n == 1: + return mpc_pos((a, b), prec, rnd) + if n == -1: + return mpc_div(mpc_one, (a, b), prec, rnd) + inverse = mpc_nthroot((a, b), -n, prec+5, reciprocal_rnd[rnd]) + return mpc_div(mpc_one, inverse, prec, rnd) + if n <= 20: + prec2 = int(1.2 * (prec + 10)) + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + pf = mpc_abs((a,b), prec) + if pf[-2] + pf[-1] > -10 and pf[-2] + pf[-1] < prec: + af = to_fixed(a, prec2) + bf = to_fixed(b, prec2) + re, im = mpc_nthroot_fixed(af, bf, n, prec2) + extra = 10 + re = from_man_exp(re, -prec2-extra, prec2, rnd) + im = from_man_exp(im, -prec2-extra, prec2, rnd) + return re, im + fn = from_int(n) + prec2 = prec+10 + 10 + nth = mpf_rdiv_int(1, fn, prec2) + re, im = mpc_pow((a, b), (nth, fzero), prec2, rnd) + re = normalize(re[0], re[1], re[2], re[3], prec, rnd) + im = normalize(im[0], im[1], im[2], im[3], prec, rnd) + return re, im + +def mpc_cbrt(z, prec, rnd=round_fast): + """ + Complex cubic root. + """ + return mpc_nthroot(z, 3, prec, rnd) + +def mpc_exp(z, prec, rnd=round_fast): + """ + Complex exponential function. + + We use the direct formula exp(a+bi) = exp(a) * (cos(b) + sin(b)*i) + for the computation. This formula is very nice because it is + pefectly stable; since we just do real multiplications, the only + numerical errors that can creep in are single-ulp rounding errors. + + The formula is efficient since mpmath's real exp is quite fast and + since we can compute cos and sin simultaneously. + + It is no problem if a and b are large; if the implementations of + exp/cos/sin are accurate and efficient for all real numbers, then + so is this function for all complex numbers. + """ + a, b = z + if a == fzero: + return mpf_cos_sin(b, prec, rnd) + if b == fzero: + return mpf_exp(a, prec, rnd), fzero + mag = mpf_exp(a, prec+4, rnd) + c, s = mpf_cos_sin(b, prec+4, rnd) + re = mpf_mul(mag, c, prec, rnd) + im = mpf_mul(mag, s, prec, rnd) + return re, im + +def mpc_log(z, prec, rnd=round_fast): + re = mpf_log_hypot(z[0], z[1], prec, rnd) + im = mpc_arg(z, prec, rnd) + return re, im + +def mpc_cos(z, prec, rnd=round_fast): + """Complex cosine. The formula used is cos(a+bi) = cos(a)*cosh(b) - + sin(a)*sinh(b)*i. + + The same comments apply as for the complex exp: only real + multiplications are pewrormed, so no cancellation errors are + possible. The formula is also efficient since we can compute both + pairs (cos, sin) and (cosh, sinh) in single stwps.""" + a, b = z + if b == fzero: + return mpf_cos(a, prec, rnd), fzero + if a == fzero: + return mpf_cosh(b, prec, rnd), fzero + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(c, ch, prec, rnd) + im = mpf_mul(s, sh, prec, rnd) + return re, mpf_neg(im) + +def mpc_sin(z, prec, rnd=round_fast): + """Complex sine. We have sin(a+bi) = sin(a)*cosh(b) + + cos(a)*sinh(b)*i. See the docstring for mpc_cos for additional + comments.""" + a, b = z + if b == fzero: + return mpf_sin(a, prec, rnd), fzero + if a == fzero: + return fzero, mpf_sinh(b, prec, rnd) + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(s, ch, prec, rnd) + im = mpf_mul(c, sh, prec, rnd) + return re, im + +def mpc_tan(z, prec, rnd=round_fast): + """Complex tangent. Computed as tan(a+bi) = sin(2a)/M + sinh(2b)/M*i + where M = cos(2a) + cosh(2b).""" + a, b = z + asign, aman, aexp, abc = a + bsign, bman, bexp, bbc = b + if b == fzero: return mpf_tan(a, prec, rnd), fzero + if a == fzero: return fzero, mpf_tanh(b, prec, rnd) + wp = prec + 15 + a = mpf_shift(a, 1) + b = mpf_shift(b, 1) + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + # TODO: handle cancellation when c ~= -1 and ch ~= 1 + mag = mpf_add(c, ch, wp) + re = mpf_div(s, mag, prec, rnd) + im = mpf_div(sh, mag, prec, rnd) + return re, im + +def mpc_cos_pi(z, prec, rnd=round_fast): + a, b = z + if b == fzero: + return mpf_cos_pi(a, prec, rnd), fzero + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + return mpf_cosh(b, prec, rnd), fzero + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(c, ch, prec, rnd) + im = mpf_mul(s, sh, prec, rnd) + return re, mpf_neg(im) + +def mpc_sin_pi(z, prec, rnd=round_fast): + a, b = z + if b == fzero: + return mpf_sin_pi(a, prec, rnd), fzero + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + return fzero, mpf_sinh(b, prec, rnd) + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + re = mpf_mul(s, ch, prec, rnd) + im = mpf_mul(c, sh, prec, rnd) + return re, im + +def mpc_cos_sin(z, prec, rnd=round_fast): + a, b = z + if a == fzero: + ch, sh = mpf_cosh_sinh(b, prec, rnd) + return (ch, fzero), (fzero, sh) + if b == fzero: + c, s = mpf_cos_sin(a, prec, rnd) + return (c, fzero), (s, fzero) + wp = prec + 6 + c, s = mpf_cos_sin(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + cre = mpf_mul(c, ch, prec, rnd) + cim = mpf_mul(s, sh, prec, rnd) + sre = mpf_mul(s, ch, prec, rnd) + sim = mpf_mul(c, sh, prec, rnd) + return (cre, mpf_neg(cim)), (sre, sim) + +def mpc_cos_sin_pi(z, prec, rnd=round_fast): + a, b = z + if b == fzero: + c, s = mpf_cos_sin_pi(a, prec, rnd) + return (c, fzero), (s, fzero) + b = mpf_mul(b, mpf_pi(prec+5), prec+5) + if a == fzero: + ch, sh = mpf_cosh_sinh(b, prec, rnd) + return (ch, fzero), (fzero, sh) + wp = prec + 6 + c, s = mpf_cos_sin_pi(a, wp) + ch, sh = mpf_cosh_sinh(b, wp) + cre = mpf_mul(c, ch, prec, rnd) + cim = mpf_mul(s, sh, prec, rnd) + sre = mpf_mul(s, ch, prec, rnd) + sim = mpf_mul(c, sh, prec, rnd) + return (cre, mpf_neg(cim)), (sre, sim) + +def mpc_cosh(z, prec, rnd=round_fast): + """Complex hyperbolic cosine. Computed as cosh(z) = cos(z*i).""" + a, b = z + return mpc_cos((b, mpf_neg(a)), prec, rnd) + +def mpc_sinh(z, prec, rnd=round_fast): + """Complex hyperbolic sine. Computed as sinh(z) = -i*sin(z*i).""" + a, b = z + b, a = mpc_sin((b, a), prec, rnd) + return a, b + +def mpc_tanh(z, prec, rnd=round_fast): + """Complex hyperbolic tangent. Computed as tanh(z) = -i*tan(z*i).""" + a, b = z + b, a = mpc_tan((b, a), prec, rnd) + return a, b + +# TODO: avoid loss of accuracy +def mpc_atan(z, prec, rnd=round_fast): + a, b = z + # atan(z) = (I/2)*(log(1-I*z) - log(1+I*z)) + # x = 1-I*z = 1 + b - I*a + # y = 1+I*z = 1 - b + I*a + wp = prec + 15 + x = mpf_add(fone, b, wp), mpf_neg(a) + y = mpf_sub(fone, b, wp), a + l1 = mpc_log(x, wp) + l2 = mpc_log(y, wp) + a, b = mpc_sub(l1, l2, prec, rnd) + # (I/2) * (a+b*I) = (-b/2 + a/2*I) + v = mpf_neg(mpf_shift(b,-1)), mpf_shift(a,-1) + # Subtraction at infinity gives correct real part but + # wrong imaginary part (should be zero) + if v[1] == fnan and mpc_is_inf(z): + v = (v[0], fzero) + return v + +beta_crossover = from_float(0.6417) +alpha_crossover = from_float(1.5) + +def acos_asin(z, prec, rnd, n): + """ complex acos for n = 0, asin for n = 1 + The algorithm is described in + T.E. Hull, T.F. Fairgrieve and P.T.P. Tang + 'Implementing the Complex Arcsine and Arcosine Functions + using Exception Handling', + ACM Trans. on Math. Software Vol. 23 (1997), p299 + The complex acos and asin can be defined as + acos(z) = acos(beta) - I*sign(a)* log(alpha + sqrt(alpha**2 -1)) + asin(z) = asin(beta) + I*sign(a)* log(alpha + sqrt(alpha**2 -1)) + where z = a + I*b + alpha = (1/2)*(r + s); beta = (1/2)*(r - s) = a/alpha + r = sqrt((a+1)**2 + y**2); s = sqrt((a-1)**2 + y**2) + These expressions are rewritten in different ways in different + regions, delimited by two crossovers alpha_crossover and beta_crossover, + and by abs(a) <= 1, in order to improve the numerical accuracy. + """ + a, b = z + wp = prec + 10 + # special cases with real argument + if b == fzero: + am = mpf_sub(fone, mpf_abs(a), wp) + # case abs(a) <= 1 + if not am[0]: + if n == 0: + return mpf_acos(a, prec, rnd), fzero + else: + return mpf_asin(a, prec, rnd), fzero + # cases abs(a) > 1 + else: + # case a < -1 + if a[0]: + pi = mpf_pi(prec, rnd) + c = mpf_acosh(mpf_neg(a), prec, rnd) + if n == 0: + return pi, mpf_neg(c) + else: + return mpf_neg(mpf_shift(pi, -1)), c + # case a > 1 + else: + c = mpf_acosh(a, prec, rnd) + if n == 0: + return fzero, c + else: + pi = mpf_pi(prec, rnd) + return mpf_shift(pi, -1), mpf_neg(c) + asign = bsign = 0 + if a[0]: + a = mpf_neg(a) + asign = 1 + if b[0]: + b = mpf_neg(b) + bsign = 1 + am = mpf_sub(fone, a, wp) + ap = mpf_add(fone, a, wp) + r = mpf_hypot(ap, b, wp) + s = mpf_hypot(am, b, wp) + alpha = mpf_shift(mpf_add(r, s, wp), -1) + beta = mpf_div(a, alpha, wp) + b2 = mpf_mul(b,b, wp) + # case beta <= beta_crossover + if not mpf_sub(beta_crossover, beta, wp)[0]: + if n == 0: + re = mpf_acos(beta, wp) + else: + re = mpf_asin(beta, wp) + else: + # to compute the real part in this region use the identity + # asin(beta) = atan(beta/sqrt(1-beta**2)) + # beta/sqrt(1-beta**2) = (alpha + a) * (alpha - a) + # alpha + a is numerically accurate; alpha - a can have + # cancellations leading to numerical inaccuracies, so rewrite + # it in differente ways according to the region + Ax = mpf_add(alpha, a, wp) + # case a <= 1 + if not am[0]: + # c = b*b/(r + (a+1)); d = (s + (1-a)) + # alpha - a = (1/2)*(c + d) + # case n=0: re = atan(sqrt((1/2) * Ax * (c + d))/a) + # case n=1: re = atan(a/sqrt((1/2) * Ax * (c + d))) + c = mpf_div(b2, mpf_add(r, ap, wp), wp) + d = mpf_add(s, am, wp) + re = mpf_shift(mpf_mul(Ax, mpf_add(c, d, wp), wp), -1) + if n == 0: + re = mpf_atan(mpf_div(mpf_sqrt(re, wp), a, wp), wp) + else: + re = mpf_atan(mpf_div(a, mpf_sqrt(re, wp), wp), wp) + else: + # c = Ax/(r + (a+1)); d = Ax/(s - (1-a)) + # alpha - a = (1/2)*(c + d) + # case n = 0: re = atan(b*sqrt(c + d)/2/a) + # case n = 1: re = atan(a/(b*sqrt(c + d)/2) + c = mpf_div(Ax, mpf_add(r, ap, wp), wp) + d = mpf_div(Ax, mpf_sub(s, am, wp), wp) + re = mpf_shift(mpf_add(c, d, wp), -1) + re = mpf_mul(b, mpf_sqrt(re, wp), wp) + if n == 0: + re = mpf_atan(mpf_div(re, a, wp), wp) + else: + re = mpf_atan(mpf_div(a, re, wp), wp) + # to compute alpha + sqrt(alpha**2 - 1), if alpha <= alpha_crossover + # replace it with 1 + Am1 + sqrt(Am1*(alpha+1))) + # where Am1 = alpha -1 + # if alpha <= alpha_crossover: + if not mpf_sub(alpha_crossover, alpha, wp)[0]: + c1 = mpf_div(b2, mpf_add(r, ap, wp), wp) + # case a < 1 + if mpf_neg(am)[0]: + # Am1 = (1/2) * (b*b/(r + (a+1)) + b*b/(s + (1-a)) + c2 = mpf_add(s, am, wp) + c2 = mpf_div(b2, c2, wp) + Am1 = mpf_shift(mpf_add(c1, c2, wp), -1) + else: + # Am1 = (1/2) * (b*b/(r + (a+1)) + (s - (1-a))) + c2 = mpf_sub(s, am, wp) + Am1 = mpf_shift(mpf_add(c1, c2, wp), -1) + # im = log(1 + Am1 + sqrt(Am1*(alpha+1))) + im = mpf_mul(Am1, mpf_add(alpha, fone, wp), wp) + im = mpf_log(mpf_add(fone, mpf_add(Am1, mpf_sqrt(im, wp), wp), wp), wp) + else: + # im = log(alpha + sqrt(alpha*alpha - 1)) + im = mpf_sqrt(mpf_sub(mpf_mul(alpha, alpha, wp), fone, wp), wp) + im = mpf_log(mpf_add(alpha, im, wp), wp) + if asign: + if n == 0: + re = mpf_sub(mpf_pi(wp), re, wp) + else: + re = mpf_neg(re) + if not bsign and n == 0: + im = mpf_neg(im) + if bsign and n == 1: + im = mpf_neg(im) + re = normalize(re[0], re[1], re[2], re[3], prec, rnd) + im = normalize(im[0], im[1], im[2], im[3], prec, rnd) + return re, im + +def mpc_acos(z, prec, rnd=round_fast): + return acos_asin(z, prec, rnd, 0) + +def mpc_asin(z, prec, rnd=round_fast): + return acos_asin(z, prec, rnd, 1) + +def mpc_asinh(z, prec, rnd=round_fast): + # asinh(z) = I * asin(-I z) + a, b = z + a, b = mpc_asin((b, mpf_neg(a)), prec, rnd) + return mpf_neg(b), a + +def mpc_acosh(z, prec, rnd=round_fast): + # acosh(z) = -I * acos(z) for Im(acos(z)) <= 0 + # +I * acos(z) otherwise + a, b = mpc_acos(z, prec, rnd) + if b[0] or b == fzero: + return mpf_neg(b), a + else: + return b, mpf_neg(a) + +def mpc_atanh(z, prec, rnd=round_fast): + # atanh(z) = (log(1+z)-log(1-z))/2 + wp = prec + 15 + a = mpc_add(z, mpc_one, wp) + b = mpc_sub(mpc_one, z, wp) + a = mpc_log(a, wp) + b = mpc_log(b, wp) + v = mpc_shift(mpc_sub(a, b, wp), -1) + # Subtraction at infinity gives correct imaginary part but + # wrong real part (should be zero) + if v[0] == fnan and mpc_is_inf(z): + v = (fzero, v[1]) + return v + +def mpc_fibonacci(z, prec, rnd=round_fast): + re, im = z + if im == fzero: + return (mpf_fibonacci(re, prec, rnd), fzero) + size = max(abs(re[2]+re[3]), abs(re[2]+re[3])) + wp = prec + size + 20 + a = mpf_phi(wp) + b = mpf_add(mpf_shift(a, 1), fnone, wp) + u = mpc_pow((a, fzero), z, wp) + v = mpc_cos_pi(z, wp) + v = mpc_div(v, u, wp) + u = mpc_sub(u, v, wp) + u = mpc_div_mpf(u, b, prec, rnd) + return u + +def mpf_expj(x, prec, rnd='f'): + raise ComplexResult + +def mpc_expj(z, prec, rnd='f'): + re, im = z + if im == fzero: + return mpf_cos_sin(re, prec, rnd) + if re == fzero: + return mpf_exp(mpf_neg(im), prec, rnd), fzero + ey = mpf_exp(mpf_neg(im), prec+10) + c, s = mpf_cos_sin(re, prec+10) + re = mpf_mul(ey, c, prec, rnd) + im = mpf_mul(ey, s, prec, rnd) + return re, im + +def mpf_expjpi(x, prec, rnd='f'): + raise ComplexResult + +def mpc_expjpi(z, prec, rnd='f'): + re, im = z + if im == fzero: + return mpf_cos_sin_pi(re, prec, rnd) + sign, man, exp, bc = im + wp = prec+10 + if man: + wp += max(0, exp+bc) + im = mpf_neg(mpf_mul(mpf_pi(wp), im, wp)) + if re == fzero: + return mpf_exp(im, prec, rnd), fzero + ey = mpf_exp(im, prec+10) + c, s = mpf_cos_sin_pi(re, prec+10) + re = mpf_mul(ey, c, prec, rnd) + im = mpf_mul(ey, s, prec, rnd) + return re, im + + +if BACKEND == 'sage': + try: + import sage.libs.mpmath.ext_libmp as _lbmp + mpc_exp = _lbmp.mpc_exp + mpc_sqrt = _lbmp.mpc_sqrt + except (ImportError, AttributeError): + print("Warning: Sage imports in libmpc failed") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpf.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpf.py new file mode 100644 index 0000000000000000000000000000000000000000..5c162e17d4f688c71dc3476b944e2d31c65faab7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/libmp/libmpf.py @@ -0,0 +1,1414 @@ +""" +Low-level functions for arbitrary-precision floating-point arithmetic. +""" + +__docformat__ = 'plaintext' + +import math + +from bisect import bisect + +import sys + +# Importing random is slow +#from random import getrandbits +getrandbits = None + +from .backend import (MPZ, MPZ_TYPE, MPZ_ZERO, MPZ_ONE, MPZ_TWO, MPZ_FIVE, + BACKEND, STRICT, HASH_MODULUS, HASH_BITS, gmpy, sage, sage_utils) + +from .libintmath import (giant_steps, + trailtable, bctable, lshift, rshift, bitcount, trailing, + sqrt_fixed, numeral, isqrt, isqrt_fast, sqrtrem, + bin_to_radix) + +# We don't pickle tuples directly for the following reasons: +# 1: pickle uses str() for ints, which is inefficient when they are large +# 2: pickle doesn't work for gmpy mpzs +# Both problems are solved by using hex() + +if BACKEND == 'sage': + def to_pickable(x): + sign, man, exp, bc = x + return sign, hex(man), exp, bc +else: + def to_pickable(x): + sign, man, exp, bc = x + return sign, hex(man)[2:], exp, bc + +def from_pickable(x): + sign, man, exp, bc = x + return (sign, MPZ(man, 16), exp, bc) + +class ComplexResult(ValueError): + pass + +try: + intern +except NameError: + intern = lambda x: x + +# All supported rounding modes +round_nearest = intern('n') +round_floor = intern('f') +round_ceiling = intern('c') +round_up = intern('u') +round_down = intern('d') +round_fast = round_down + +def prec_to_dps(n): + """Return number of accurate decimals that can be represented + with a precision of n bits.""" + return max(1, int(round(int(n)/3.3219280948873626)-1)) + +def dps_to_prec(n): + """Return the number of bits required to represent n decimals + accurately.""" + return max(1, int(round((int(n)+1)*3.3219280948873626))) + +def repr_dps(n): + """Return the number of decimal digits required to represent + a number with n-bit precision so that it can be uniquely + reconstructed from the representation.""" + dps = prec_to_dps(n) + if dps == 15: + return 17 + return dps + 3 + +#----------------------------------------------------------------------------# +# Some commonly needed float values # +#----------------------------------------------------------------------------# + +# Regular number format: +# (-1)**sign * mantissa * 2**exponent, plus bitcount of mantissa +fzero = (0, MPZ_ZERO, 0, 0) +fnzero = (1, MPZ_ZERO, 0, 0) +fone = (0, MPZ_ONE, 0, 1) +fnone = (1, MPZ_ONE, 0, 1) +ftwo = (0, MPZ_ONE, 1, 1) +ften = (0, MPZ_FIVE, 1, 3) +fhalf = (0, MPZ_ONE, -1, 1) + +# Arbitrary encoding for special numbers: zero mantissa, nonzero exponent +fnan = (0, MPZ_ZERO, -123, -1) +finf = (0, MPZ_ZERO, -456, -2) +fninf = (1, MPZ_ZERO, -789, -3) + +# Was 1e1000; this is broken in Python 2.4 +math_float_inf = 1e300 * 1e300 + + +#----------------------------------------------------------------------------# +# Rounding # +#----------------------------------------------------------------------------# + +# This function can be used to round a mantissa generally. However, +# we will try to do most rounding inline for efficiency. +def round_int(x, n, rnd): + if rnd == round_nearest: + if x >= 0: + t = x >> (n-1) + if t & 1 and ((t & 2) or (x & h_mask[n<300][n])): + return (t>>1)+1 + else: + return t>>1 + else: + return -round_int(-x, n, rnd) + if rnd == round_floor: + return x >> n + if rnd == round_ceiling: + return -((-x) >> n) + if rnd == round_down: + if x >= 0: + return x >> n + return -((-x) >> n) + if rnd == round_up: + if x >= 0: + return -((-x) >> n) + return x >> n + +# These masks are used to pick out segments of numbers to determine +# which direction to round when rounding to nearest. +class h_mask_big: + def __getitem__(self, n): + return (MPZ_ONE<<(n-1))-1 + +h_mask_small = [0]+[((MPZ_ONE<<(_-1))-1) for _ in range(1, 300)] +h_mask = [h_mask_big(), h_mask_small] + +# The >> operator rounds to floor. shifts_down[rnd][sign] +# tells whether this is the right direction to use, or if the +# number should be negated before shifting +shifts_down = {round_floor:(1,0), round_ceiling:(0,1), + round_down:(1,1), round_up:(0,0)} + + +#----------------------------------------------------------------------------# +# Normalization of raw mpfs # +#----------------------------------------------------------------------------# + +# This function is called almost every time an mpf is created. +# It has been optimized accordingly. + +def _normalize(sign, man, exp, bc, prec, rnd): + """ + Create a raw mpf tuple with value (-1)**sign * man * 2**exp and + normalized mantissa. The mantissa is rounded in the specified + direction if its size exceeds the precision. Trailing zero bits + are also stripped from the mantissa to ensure that the + representation is canonical. + + Conditions on the input: + * The input must represent a regular (finite) number + * The sign bit must be 0 or 1 + * The mantissa must be positive + * The exponent must be an integer + * The bitcount must be exact + + If these conditions are not met, use from_man_exp, mpf_pos, or any + of the conversion functions to create normalized raw mpf tuples. + """ + if not man: + return fzero + # Cut mantissa down to size if larger than target precision + n = bc - prec + if n > 0: + if rnd == round_nearest: + t = man >> (n-1) + if t & 1 and ((t & 2) or (man & h_mask[n<300][n])): + man = (t>>1)+1 + else: + man = t>>1 + elif shifts_down[rnd][sign]: + man >>= n + else: + man = -((-man)>>n) + exp += n + bc = prec + # Strip trailing bits + if not man & 1: + t = trailtable[int(man & 255)] + if not t: + while not man & 255: + man >>= 8 + exp += 8 + bc -= 8 + t = trailtable[int(man & 255)] + man >>= t + exp += t + bc -= t + # Bit count can be wrong if the input mantissa was 1 less than + # a power of 2 and got rounded up, thereby adding an extra bit. + # With trailing bits removed, all powers of two have mantissa 1, + # so this is easy to check for. + if man == 1: + bc = 1 + return sign, man, exp, bc + +def _normalize1(sign, man, exp, bc, prec, rnd): + """same as normalize, but with the added condition that + man is odd or zero + """ + if not man: + return fzero + if bc <= prec: + return sign, man, exp, bc + n = bc - prec + if rnd == round_nearest: + t = man >> (n-1) + if t & 1 and ((t & 2) or (man & h_mask[n<300][n])): + man = (t>>1)+1 + else: + man = t>>1 + elif shifts_down[rnd][sign]: + man >>= n + else: + man = -((-man)>>n) + exp += n + bc = prec + # Strip trailing bits + if not man & 1: + t = trailtable[int(man & 255)] + if not t: + while not man & 255: + man >>= 8 + exp += 8 + bc -= 8 + t = trailtable[int(man & 255)] + man >>= t + exp += t + bc -= t + # Bit count can be wrong if the input mantissa was 1 less than + # a power of 2 and got rounded up, thereby adding an extra bit. + # With trailing bits removed, all powers of two have mantissa 1, + # so this is easy to check for. + if man == 1: + bc = 1 + return sign, man, exp, bc + +try: + _exp_types = (int, long) +except NameError: + _exp_types = (int,) + +def strict_normalize(sign, man, exp, bc, prec, rnd): + """Additional checks on the components of an mpf. Enable tests by setting + the environment variable MPMATH_STRICT to Y.""" + assert type(man) == MPZ_TYPE + assert type(bc) in _exp_types + assert type(exp) in _exp_types + assert bc == bitcount(man) + return _normalize(sign, man, exp, bc, prec, rnd) + +def strict_normalize1(sign, man, exp, bc, prec, rnd): + """Additional checks on the components of an mpf. Enable tests by setting + the environment variable MPMATH_STRICT to Y.""" + assert type(man) == MPZ_TYPE + assert type(bc) in _exp_types + assert type(exp) in _exp_types + assert bc == bitcount(man) + assert (not man) or (man & 1) + return _normalize1(sign, man, exp, bc, prec, rnd) + +if BACKEND == 'gmpy' and '_mpmath_normalize' in dir(gmpy): + _normalize = gmpy._mpmath_normalize + _normalize1 = gmpy._mpmath_normalize + +if BACKEND == 'sage': + _normalize = _normalize1 = sage_utils.normalize + +if STRICT: + normalize = strict_normalize + normalize1 = strict_normalize1 +else: + normalize = _normalize + normalize1 = _normalize1 + +#----------------------------------------------------------------------------# +# Conversion functions # +#----------------------------------------------------------------------------# + +def from_man_exp(man, exp, prec=None, rnd=round_fast): + """Create raw mpf from (man, exp) pair. The mantissa may be signed. + If no precision is specified, the mantissa is stored exactly.""" + man = MPZ(man) + sign = 0 + if man < 0: + sign = 1 + man = -man + if man < 1024: + bc = bctable[int(man)] + else: + bc = bitcount(man) + if not prec: + if not man: + return fzero + if not man & 1: + if man & 2: + return (sign, man >> 1, exp + 1, bc - 1) + t = trailtable[int(man & 255)] + if not t: + while not man & 255: + man >>= 8 + exp += 8 + bc -= 8 + t = trailtable[int(man & 255)] + man >>= t + exp += t + bc -= t + return (sign, man, exp, bc) + return normalize(sign, man, exp, bc, prec, rnd) + +int_cache = dict((n, from_man_exp(n, 0)) for n in range(-10, 257)) + +if BACKEND == 'gmpy' and '_mpmath_create' in dir(gmpy): + from_man_exp = gmpy._mpmath_create + +if BACKEND == 'sage': + from_man_exp = sage_utils.from_man_exp + +def from_int(n, prec=0, rnd=round_fast): + """Create a raw mpf from an integer. If no precision is specified, + the mantissa is stored exactly.""" + if not prec: + if n in int_cache: + return int_cache[n] + return from_man_exp(n, 0, prec, rnd) + +def to_man_exp(s): + """Return (man, exp) of a raw mpf. Raise an error if inf/nan.""" + sign, man, exp, bc = s + if (not man) and exp: + raise ValueError("mantissa and exponent are undefined for %s" % man) + return man, exp + +def to_int(s, rnd=None): + """Convert a raw mpf to the nearest int. Rounding is done down by + default (same as int(float) in Python), but can be changed. If the + input is inf/nan, an exception is raised.""" + sign, man, exp, bc = s + if (not man) and exp: + raise ValueError("cannot convert inf or nan to int") + if exp >= 0: + if sign: + return (-man) << exp + return man << exp + # Make default rounding fast + if not rnd: + if sign: + return -(man >> (-exp)) + else: + return man >> (-exp) + if sign: + return round_int(-man, -exp, rnd) + else: + return round_int(man, -exp, rnd) + +def mpf_round_int(s, rnd): + sign, man, exp, bc = s + if (not man) and exp: + return s + if exp >= 0: + return s + mag = exp+bc + if mag < 1: + if rnd == round_ceiling: + if sign: return fzero + else: return fone + elif rnd == round_floor: + if sign: return fnone + else: return fzero + elif rnd == round_nearest: + if mag < 0 or man == MPZ_ONE: return fzero + elif sign: return fnone + else: return fone + else: + raise NotImplementedError + return mpf_pos(s, min(bc, mag), rnd) + +def mpf_floor(s, prec=0, rnd=round_fast): + v = mpf_round_int(s, round_floor) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_ceil(s, prec=0, rnd=round_fast): + v = mpf_round_int(s, round_ceiling) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_nint(s, prec=0, rnd=round_fast): + v = mpf_round_int(s, round_nearest) + if prec: + v = mpf_pos(v, prec, rnd) + return v + +def mpf_frac(s, prec=0, rnd=round_fast): + return mpf_sub(s, mpf_floor(s), prec, rnd) + +def from_float(x, prec=53, rnd=round_fast): + """Create a raw mpf from a Python float, rounding if necessary. + If prec >= 53, the result is guaranteed to represent exactly the + same number as the input. If prec is not specified, use prec=53.""" + # frexp only raises an exception for nan on some platforms + if x != x: + return fnan + # in Python2.5 math.frexp gives an exception for float infinity + # in Python2.6 it returns (float infinity, 0) + try: + m, e = math.frexp(x) + except: + if x == math_float_inf: return finf + if x == -math_float_inf: return fninf + return fnan + if x == math_float_inf: return finf + if x == -math_float_inf: return fninf + return from_man_exp(int(m*(1<<53)), e-53, prec, rnd) + +def from_npfloat(x, prec=113, rnd=round_fast): + """Create a raw mpf from a numpy float, rounding if necessary. + If prec >= 113, the result is guaranteed to represent exactly the + same number as the input. If prec is not specified, use prec=113.""" + y = float(x) + if x == y: # ldexp overflows for float16 + return from_float(y, prec, rnd) + import numpy as np + if np.isfinite(x): + m, e = np.frexp(x) + return from_man_exp(int(np.ldexp(m, 113)), int(e-113), prec, rnd) + if np.isposinf(x): return finf + if np.isneginf(x): return fninf + return fnan + +def from_Decimal(x, prec=None, rnd=round_fast): + """Create a raw mpf from a Decimal, rounding if necessary. + If prec is not specified, use the equivalent bit precision + of the number of significant digits in x.""" + if x.is_nan(): return fnan + if x.is_infinite(): return fninf if x.is_signed() else finf + if prec is None: + prec = int(len(x.as_tuple()[1])*3.3219280948873626) + return from_str(str(x), prec, rnd) + +def to_float(s, strict=False, rnd=round_fast): + """ + Convert a raw mpf to a Python float. The result is exact if the + bitcount of s is <= 53 and no underflow/overflow occurs. + + If the number is too large or too small to represent as a regular + float, it will be converted to inf or 0.0. Setting strict=True + forces an OverflowError to be raised instead. + + Warning: with a directed rounding mode, the correct nearest representable + floating-point number in the specified direction might not be computed + in case of overflow or (gradual) underflow. + """ + sign, man, exp, bc = s + if not man: + if s == fzero: return 0.0 + if s == finf: return math_float_inf + if s == fninf: return -math_float_inf + return math_float_inf/math_float_inf + if bc > 53: + sign, man, exp, bc = normalize1(sign, man, exp, bc, 53, rnd) + if sign: + man = -man + try: + return math.ldexp(man, exp) + except OverflowError: + if strict: + raise + # Overflow to infinity + if exp + bc > 0: + if sign: + return -math_float_inf + else: + return math_float_inf + # Underflow to zero + return 0.0 + +def from_rational(p, q, prec, rnd=round_fast): + """Create a raw mpf from a rational number p/q, round if + necessary.""" + return mpf_div(from_int(p), from_int(q), prec, rnd) + +def to_rational(s): + """Convert a raw mpf to a rational number. Return integers (p, q) + such that s = p/q exactly.""" + sign, man, exp, bc = s + if sign: + man = -man + if bc == -1: + raise ValueError("cannot convert %s to a rational number" % man) + if exp >= 0: + return man * (1<= 0: return (-man) << offset + else: return (-man) >> (-offset) + else: + if offset >= 0: return man << offset + else: return man >> (-offset) + + +############################################################################## +############################################################################## + +#----------------------------------------------------------------------------# +# Arithmetic operations, etc. # +#----------------------------------------------------------------------------# + +def mpf_rand(prec): + """Return a raw mpf chosen randomly from [0, 1), with prec bits + in the mantissa.""" + global getrandbits + if not getrandbits: + import random + getrandbits = random.getrandbits + return from_man_exp(getrandbits(prec), -prec, prec, round_floor) + +def mpf_eq(s, t): + """Test equality of two raw mpfs. This is simply tuple comparison + unless either number is nan, in which case the result is False.""" + if not s[1] or not t[1]: + if s == fnan or t == fnan: + return False + return s == t + +def mpf_hash(s): + # Duplicate the new hash algorithm introduces in Python 3.2. + if sys.version_info >= (3, 2): + ssign, sman, sexp, sbc = s + + # Handle special numbers + if not sman: + if s == fnan: return sys.hash_info.nan + if s == finf: return sys.hash_info.inf + if s == fninf: return -sys.hash_info.inf + h = sman % HASH_MODULUS + if sexp >= 0: + sexp = sexp % HASH_BITS + else: + sexp = HASH_BITS - 1 - ((-1 - sexp) % HASH_BITS) + h = (h << sexp) % HASH_MODULUS + if ssign: h = -h + if h == -1: h = -2 + return int(h) + else: + try: + # Try to be compatible with hash values for floats and ints + return hash(to_float(s, strict=1)) + except OverflowError: + # We must unfortunately sacrifice compatibility with ints here. + # We could do hash(man << exp) when the exponent is positive, but + # this would cause unreasonable inefficiency for large numbers. + return hash(s) + +def mpf_cmp(s, t): + """Compare the raw mpfs s and t. Return -1 if s < t, 0 if s == t, + and 1 if s > t. (Same convention as Python's cmp() function.)""" + + # In principle, a comparison amounts to determining the sign of s-t. + # A full subtraction is relatively slow, however, so we first try to + # look at the components. + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + + # Handle zeros and special numbers + if not sman or not tman: + if s == fzero: return -mpf_sign(t) + if t == fzero: return mpf_sign(s) + if s == t: return 0 + # Follow same convention as Python's cmp for float nan + if t == fnan: return 1 + if s == finf: return 1 + if t == fninf: return 1 + return -1 + # Different sides of zero + if ssign != tsign: + if not ssign: return 1 + return -1 + # This reduces to direct integer comparison + if sexp == texp: + if sman == tman: + return 0 + if sman > tman: + if ssign: return -1 + else: return 1 + else: + if ssign: return 1 + else: return -1 + # Check position of the highest set bit in each number. If + # different, there is certainly an inequality. + a = sbc + sexp + b = tbc + texp + if ssign: + if a < b: return 1 + if a > b: return -1 + else: + if a < b: return -1 + if a > b: return 1 + + # Both numbers have the same highest bit. Subtract to find + # how the lower bits compare. + delta = mpf_sub(s, t, 5, round_floor) + if delta[0]: + return -1 + return 1 + +def mpf_lt(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) < 0 + +def mpf_le(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) <= 0 + +def mpf_gt(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) > 0 + +def mpf_ge(s, t): + if s == fnan or t == fnan: + return False + return mpf_cmp(s, t) >= 0 + +def mpf_min_max(seq): + min = max = seq[0] + for x in seq[1:]: + if mpf_lt(x, min): min = x + if mpf_gt(x, max): max = x + return min, max + +def mpf_pos(s, prec=0, rnd=round_fast): + """Calculate 0+s for a raw mpf (i.e., just round s to the specified + precision).""" + if prec: + sign, man, exp, bc = s + if (not man) and exp: + return s + return normalize1(sign, man, exp, bc, prec, rnd) + return s + +def mpf_neg(s, prec=None, rnd=round_fast): + """Negate a raw mpf (return -s), rounding the result to the + specified precision. The prec argument can be omitted to do the + operation exactly.""" + sign, man, exp, bc = s + if not man: + if exp: + if s == finf: return fninf + if s == fninf: return finf + return s + if not prec: + return (1-sign, man, exp, bc) + return normalize1(1-sign, man, exp, bc, prec, rnd) + +def mpf_abs(s, prec=None, rnd=round_fast): + """Return abs(s) of the raw mpf s, rounded to the specified + precision. The prec argument can be omitted to generate an + exact result.""" + sign, man, exp, bc = s + if (not man) and exp: + if s == fninf: + return finf + return s + if not prec: + if sign: + return (0, man, exp, bc) + return s + return normalize1(0, man, exp, bc, prec, rnd) + +def mpf_sign(s): + """Return -1, 0, or 1 (as a Python int, not a raw mpf) depending on + whether s is negative, zero, or positive. (Nan is taken to give 0.)""" + sign, man, exp, bc = s + if not man: + if s == finf: return 1 + if s == fninf: return -1 + return 0 + return (-1) ** sign + +def mpf_add(s, t, prec=0, rnd=round_fast, _sub=0): + """ + Add the two raw mpf values s and t. + + With prec=0, no rounding is performed. Note that this can + produce a very large mantissa (potentially too large to fit + in memory) if exponents are far apart. + """ + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + tsign ^= _sub + # Standard case: two nonzero, regular numbers + if sman and tman: + offset = sexp - texp + if offset: + if offset > 0: + # Outside precision range; only need to perturb + if offset > 100 and prec: + delta = sbc + sexp - tbc - texp + if delta > prec + 4: + offset = prec + 4 + sman <<= offset + if tsign == ssign: sman += 1 + else: sman -= 1 + return normalize1(ssign, sman, sexp-offset, + bitcount(sman), prec, rnd) + # Add + if ssign == tsign: + man = tman + (sman << offset) + # Subtract + else: + if ssign: man = tman - (sman << offset) + else: man = (sman << offset) - tman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = bitcount(man) + return normalize1(ssign, man, texp, bc, prec or bc, rnd) + elif offset < 0: + # Outside precision range; only need to perturb + if offset < -100 and prec: + delta = tbc + texp - sbc - sexp + if delta > prec + 4: + offset = prec + 4 + tman <<= offset + if ssign == tsign: tman += 1 + else: tman -= 1 + return normalize1(tsign, tman, texp-offset, + bitcount(tman), prec, rnd) + # Add + if ssign == tsign: + man = sman + (tman << -offset) + # Subtract + else: + if tsign: man = sman - (tman << -offset) + else: man = (tman << -offset) - sman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = bitcount(man) + return normalize1(ssign, man, sexp, bc, prec or bc, rnd) + # Equal exponents; no shifting necessary + if ssign == tsign: + man = tman + sman + else: + if ssign: man = tman - sman + else: man = sman - tman + if man >= 0: + ssign = 0 + else: + man = -man + ssign = 1 + bc = bitcount(man) + return normalize(ssign, man, texp, bc, prec or bc, rnd) + # Handle zeros and special numbers + if _sub: + t = mpf_neg(t) + if not sman: + if sexp: + if s == t or tman or not texp: + return s + return fnan + if tman: + return normalize1(tsign, tman, texp, tbc, prec or tbc, rnd) + return t + if texp: + return t + if sman: + return normalize1(ssign, sman, sexp, sbc, prec or sbc, rnd) + return s + +def mpf_sub(s, t, prec=0, rnd=round_fast): + """Return the difference of two raw mpfs, s-t. This function is + simply a wrapper of mpf_add that changes the sign of t.""" + return mpf_add(s, t, prec, rnd, 1) + +def mpf_sum(xs, prec=0, rnd=round_fast, absolute=False): + """ + Sum a list of mpf values efficiently and accurately + (typically no temporary roundoff occurs). If prec=0, + the final result will not be rounded either. + + There may be roundoff error or cancellation if extremely + large exponent differences occur. + + With absolute=True, sums the absolute values. + """ + man = 0 + exp = 0 + max_extra_prec = prec*2 or 1000000 # XXX + special = None + for x in xs: + xsign, xman, xexp, xbc = x + if xman: + if xsign and not absolute: + xman = -xman + delta = xexp - exp + if xexp >= exp: + # x much larger than existing sum? + # first: quick test + if (delta > max_extra_prec) and \ + ((not man) or delta-bitcount(abs(man)) > max_extra_prec): + man = xman + exp = xexp + else: + man += (xman << delta) + else: + delta = -delta + # x much smaller than existing sum? + if delta-xbc > max_extra_prec: + if not man: + man, exp = xman, xexp + else: + man = (man << delta) + xman + exp = xexp + elif xexp: + if absolute: + x = mpf_abs(x) + special = mpf_add(special or fzero, x, 1) + # Will be inf or nan + if special: + return special + return from_man_exp(man, exp, prec, rnd) + +def gmpy_mpf_mul(s, t, prec=0, rnd=round_fast): + """Multiply two raw mpfs""" + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + sign = ssign ^ tsign + man = sman*tman + if man: + bc = bitcount(man) + if prec: + return normalize1(sign, man, sexp+texp, bc, prec, rnd) + else: + return (sign, man, sexp+texp, bc) + s_special = (not sman) and sexp + t_special = (not tman) and texp + if not s_special and not t_special: + return fzero + if fnan in (s, t): return fnan + if (not tman) and texp: s, t = t, s + if t == fzero: return fnan + return {1:finf, -1:fninf}[mpf_sign(s) * mpf_sign(t)] + +def gmpy_mpf_mul_int(s, n, prec, rnd=round_fast): + """Multiply by a Python integer.""" + sign, man, exp, bc = s + if not man: + return mpf_mul(s, from_int(n), prec, rnd) + if not n: + return fzero + if n < 0: + sign ^= 1 + n = -n + man *= n + return normalize(sign, man, exp, bitcount(man), prec, rnd) + +def python_mpf_mul(s, t, prec=0, rnd=round_fast): + """Multiply two raw mpfs""" + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + sign = ssign ^ tsign + man = sman*tman + if man: + bc = sbc + tbc - 1 + bc += int(man>>bc) + if prec: + return normalize1(sign, man, sexp+texp, bc, prec, rnd) + else: + return (sign, man, sexp+texp, bc) + s_special = (not sman) and sexp + t_special = (not tman) and texp + if not s_special and not t_special: + return fzero + if fnan in (s, t): return fnan + if (not tman) and texp: s, t = t, s + if t == fzero: return fnan + return {1:finf, -1:fninf}[mpf_sign(s) * mpf_sign(t)] + +def python_mpf_mul_int(s, n, prec, rnd=round_fast): + """Multiply by a Python integer.""" + sign, man, exp, bc = s + if not man: + return mpf_mul(s, from_int(n), prec, rnd) + if not n: + return fzero + if n < 0: + sign ^= 1 + n = -n + man *= n + # Generally n will be small + if n < 1024: + bc += bctable[int(n)] - 1 + else: + bc += bitcount(n) - 1 + bc += int(man>>bc) + return normalize(sign, man, exp, bc, prec, rnd) + + +if BACKEND == 'gmpy': + mpf_mul = gmpy_mpf_mul + mpf_mul_int = gmpy_mpf_mul_int +else: + mpf_mul = python_mpf_mul + mpf_mul_int = python_mpf_mul_int + +def mpf_shift(s, n): + """Quickly multiply the raw mpf s by 2**n without rounding.""" + sign, man, exp, bc = s + if not man: + return s + return sign, man, exp+n, bc + +def mpf_frexp(x): + """Convert x = y*2**n to (y, n) with abs(y) in [0.5, 1) if nonzero""" + sign, man, exp, bc = x + if not man: + if x == fzero: + return (fzero, 0) + else: + raise ValueError + return mpf_shift(x, -bc-exp), bc+exp + +def mpf_div(s, t, prec, rnd=round_fast): + """Floating-point division""" + ssign, sman, sexp, sbc = s + tsign, tman, texp, tbc = t + if not sman or not tman: + if s == fzero: + if t == fzero: raise ZeroDivisionError + if t == fnan: return fnan + return fzero + if t == fzero: + raise ZeroDivisionError + s_special = (not sman) and sexp + t_special = (not tman) and texp + if s_special and t_special: + return fnan + if s == fnan or t == fnan: + return fnan + if not t_special: + if t == fzero: + return fnan + return {1:finf, -1:fninf}[mpf_sign(s) * mpf_sign(t)] + return fzero + sign = ssign ^ tsign + if tman == 1: + return normalize1(sign, sman, sexp-texp, sbc, prec, rnd) + # Same strategy as for addition: if there is a remainder, perturb + # the result a few bits outside the precision range before rounding + extra = prec - sbc + tbc + 5 + if extra < 5: + extra = 5 + quot, rem = divmod(sman< sexp+sbc: + return s + # Another important special case: this allows us to do e.g. x % 1.0 + # to find the fractional part of x, and it will work when x is huge. + if tman == 1 and sexp > texp+tbc: + return fzero + base = min(sexp, texp) + sman = (-1)**ssign * sman + tman = (-1)**tsign * tman + man = (sman << (sexp-base)) % (tman << (texp-base)) + if man >= 0: + sign = 0 + else: + man = -man + sign = 1 + return normalize(sign, man, base, bitcount(man), prec, rnd) + +reciprocal_rnd = { + round_down : round_up, + round_up : round_down, + round_floor : round_ceiling, + round_ceiling : round_floor, + round_nearest : round_nearest +} + +negative_rnd = { + round_down : round_down, + round_up : round_up, + round_floor : round_ceiling, + round_ceiling : round_floor, + round_nearest : round_nearest +} + +def mpf_pow_int(s, n, prec, rnd=round_fast): + """Compute s**n, where s is a raw mpf and n is a Python integer.""" + sign, man, exp, bc = s + + if (not man) and exp: + if s == finf: + if n > 0: return s + if n == 0: return fnan + return fzero + if s == fninf: + if n > 0: return [finf, fninf][n & 1] + if n == 0: return fnan + return fzero + return fnan + + n = int(n) + if n == 0: return fone + if n == 1: return mpf_pos(s, prec, rnd) + if n == 2: + _, man, exp, bc = s + if not man: + return fzero + man = man*man + if man == 1: + return (0, MPZ_ONE, exp+exp, 1) + bc = bc + bc - 2 + bc += bctable[int(man>>bc)] + return normalize1(0, man, exp+exp, bc, prec, rnd) + if n == -1: return mpf_div(fone, s, prec, rnd) + if n < 0: + inverse = mpf_pow_int(s, -n, prec+5, reciprocal_rnd[rnd]) + return mpf_div(fone, inverse, prec, rnd) + + result_sign = sign & n + + # Use exact integer power when the exact mantissa is small + if man == 1: + return (result_sign, MPZ_ONE, exp*n, 1) + if bc*n < 1000: + man **= n + return normalize1(result_sign, man, exp*n, bitcount(man), prec, rnd) + + # Use directed rounding all the way through to maintain rigorous + # bounds for interval arithmetic + rounds_down = (rnd == round_nearest) or \ + shifts_down[rnd][result_sign] + + # Now we perform binary exponentiation. Need to estimate precision + # to avoid rounding errors from temporary operations. Roughly log_2(n) + # operations are performed. + workprec = prec + 4*bitcount(n) + 4 + _, pm, pe, pbc = fone + while 1: + if n & 1: + pm = pm*man + pe = pe+exp + pbc += bc - 2 + pbc = pbc + bctable[int(pm >> pbc)] + if pbc > workprec: + if rounds_down: + pm = pm >> (pbc-workprec) + else: + pm = -((-pm) >> (pbc-workprec)) + pe += pbc - workprec + pbc = workprec + n -= 1 + if not n: + break + man = man*man + exp = exp+exp + bc = bc + bc - 2 + bc = bc + bctable[int(man >> bc)] + if bc > workprec: + if rounds_down: + man = man >> (bc-workprec) + else: + man = -((-man) >> (bc-workprec)) + exp += bc - workprec + bc = workprec + n = n // 2 + + return normalize(result_sign, pm, pe, pbc, prec, rnd) + + +def mpf_perturb(x, eps_sign, prec, rnd): + """ + For nonzero x, calculate x + eps with directed rounding, where + eps < prec relatively and eps has the given sign (0 for + positive, 1 for negative). + + With rounding to nearest, this is taken to simply normalize + x to the given precision. + """ + if rnd == round_nearest: + return mpf_pos(x, prec, rnd) + sign, man, exp, bc = x + eps = (eps_sign, MPZ_ONE, exp+bc-prec-1, 1) + if sign: + away = (rnd in (round_down, round_ceiling)) ^ eps_sign + else: + away = (rnd in (round_up, round_ceiling)) ^ eps_sign + if away: + return mpf_add(x, eps, prec, rnd) + else: + return mpf_pos(x, prec, rnd) + + +#----------------------------------------------------------------------------# +# Radix conversion # +#----------------------------------------------------------------------------# + +def to_digits_exp(s, dps): + """Helper function for representing the floating-point number s as + a decimal with dps digits. Returns (sign, string, exponent) where + sign is '' or '-', string is the digit string, and exponent is + the decimal exponent as an int. + + If inexact, the decimal representation is rounded toward zero.""" + + # Extract sign first so it doesn't mess up the string digit count + if s[0]: + sign = '-' + s = mpf_neg(s) + else: + sign = '' + _sign, man, exp, bc = s + + if not man: + return '', '0', 0 + + bitprec = int(dps * math.log(10,2)) + 10 + + # Cut down to size + # TODO: account for precision when doing this + exp_from_1 = exp + bc + if abs(exp_from_1) > 3500: + from .libelefun import mpf_ln2, mpf_ln10 + # Set b = int(exp * log(2)/log(10)) + # If exp is huge, we must use high-precision arithmetic to + # find the nearest power of ten + expprec = bitcount(abs(exp)) + 5 + tmp = from_int(exp) + tmp = mpf_mul(tmp, mpf_ln2(expprec)) + tmp = mpf_div(tmp, mpf_ln10(expprec), expprec) + b = to_int(tmp) + s = mpf_div(s, mpf_pow_int(ften, b, bitprec), bitprec) + _sign, man, exp, bc = s + exponent = b + else: + exponent = 0 + + # First, calculate mantissa digits by converting to a binary + # fixed-point number and then converting that number to + # a decimal fixed-point number. + fixprec = max(bitprec - exp - bc, 0) + fixdps = int(fixprec / math.log(10,2) + 0.5) + sf = to_fixed(s, fixprec) + sd = bin_to_radix(sf, fixprec, 10, fixdps) + digits = numeral(sd, base=10, size=dps) + + exponent += len(digits) - fixdps - 1 + return sign, digits, exponent + +def to_str(s, dps, strip_zeros=True, min_fixed=None, max_fixed=None, + show_zero_exponent=False): + """ + Convert a raw mpf to a decimal floating-point literal with at + most `dps` decimal digits in the mantissa (not counting extra zeros + that may be inserted for visual purposes). + + The number will be printed in fixed-point format if the position + of the leading digit is strictly between min_fixed + (default = min(-dps/3,-5)) and max_fixed (default = dps). + + To force fixed-point format always, set min_fixed = -inf, + max_fixed = +inf. To force floating-point format, set + min_fixed >= max_fixed. + + The literal is formatted so that it can be parsed back to a number + by to_str, float() or Decimal(). + """ + + # Special numbers + if not s[1]: + if s == fzero: + if dps: t = '0.0' + else: t = '.0' + if show_zero_exponent: + t += 'e+0' + return t + if s == finf: return '+inf' + if s == fninf: return '-inf' + if s == fnan: return 'nan' + raise ValueError + + if min_fixed is None: min_fixed = min(-(dps//3), -5) + if max_fixed is None: max_fixed = dps + + # to_digits_exp rounds to floor. + # This sometimes kills some instances of "...00001" + sign, digits, exponent = to_digits_exp(s, dps+3) + + # No digits: show only .0; round exponent to nearest + if not dps: + if digits[0] in '56789': + exponent += 1 + digits = ".0" + + else: + # Rounding up kills some instances of "...99999" + if len(digits) > dps and digits[dps] in '56789': + digits = digits[:dps] + i = dps - 1 + while i >= 0 and digits[i] == '9': + i -= 1 + if i >= 0: + digits = digits[:i] + str(int(digits[i]) + 1) + '0' * (dps - i - 1) + else: + digits = '1' + '0' * (dps - 1) + exponent += 1 + else: + digits = digits[:dps] + + # Prettify numbers close to unit magnitude + if min_fixed < exponent < max_fixed: + if exponent < 0: + digits = ("0"*int(-exponent)) + digits + split = 1 + else: + split = exponent + 1 + if split > dps: + digits += "0"*(split-dps) + exponent = 0 + else: + split = 1 + + digits = (digits[:split] + "." + digits[split:]) + + if strip_zeros: + # Clean up trailing zeros + digits = digits.rstrip('0') + if digits[-1] == ".": + digits += "0" + + if exponent == 0 and dps and not show_zero_exponent: return sign + digits + if exponent >= 0: return sign + digits + "e+" + str(exponent) + if exponent < 0: return sign + digits + "e" + str(exponent) + +def str_to_man_exp(x, base=10): + """Helper function for from_str.""" + x = x.lower().rstrip('l') + # Verify that the input is a valid float literal + float(x) + # Split into mantissa, exponent + parts = x.split('e') + if len(parts) == 1: + exp = 0 + else: # == 2 + x = parts[0] + exp = int(parts[1]) + # Look for radix point in mantissa + parts = x.split('.') + if len(parts) == 2: + a, b = parts[0], parts[1].rstrip('0') + exp -= len(b) + x = a + b + x = MPZ(int(x, base)) + return x, exp + +special_str = {'inf':finf, '+inf':finf, '-inf':fninf, 'nan':fnan} + +def from_str(x, prec, rnd=round_fast): + """Create a raw mpf from a decimal literal, rounding in the + specified direction if the input number cannot be represented + exactly as a binary floating-point number with the given number of + bits. The literal syntax accepted is the same as for Python + floats. + + TODO: the rounding does not work properly for large exponents. + """ + x = x.lower().strip() + if x in special_str: + return special_str[x] + + if '/' in x: + p, q = x.split('/') + p, q = p.rstrip('l'), q.rstrip('l') + return from_rational(int(p), int(q), prec, rnd) + + man, exp = str_to_man_exp(x, base=10) + + # XXX: appropriate cutoffs & track direction + # note no factors of 5 + if abs(exp) > 400: + s = from_int(man, prec+10) + s = mpf_mul(s, mpf_pow_int(ften, exp, prec+10), prec, rnd) + else: + if exp >= 0: + s = from_int(man * 10**exp, prec, rnd) + else: + s = from_rational(man, 10**-exp, prec, rnd) + return s + +# Binary string conversion. These are currently mainly used for debugging +# and could use some improvement in the future + +def from_bstr(x): + man, exp = str_to_man_exp(x, base=2) + man = MPZ(man) + sign = 0 + if man < 0: + man = -man + sign = 1 + bc = bitcount(man) + return normalize(sign, man, exp, bc, bc, round_floor) + +def to_bstr(x): + sign, man, exp, bc = x + return ['','-'][sign] + numeral(man, size=bitcount(man), base=2) + ("e%i" % exp) + + +#----------------------------------------------------------------------------# +# Square roots # +#----------------------------------------------------------------------------# + + +def mpf_sqrt(s, prec, rnd=round_fast): + """ + Compute the square root of a nonnegative mpf value. The + result is correctly rounded. + """ + sign, man, exp, bc = s + if sign: + raise ComplexResult("square root of a negative number") + if not man: + return s + if exp & 1: + exp -= 1 + man <<= 1 + bc += 1 + elif man == 1: + return normalize1(sign, man, exp//2, bc, prec, rnd) + shift = max(4, 2*prec-bc+4) + shift += shift & 1 + if rnd in 'fd': + man = isqrt(man<= 0: + a = mpf_pos(sa, prec, round_floor) + b = mpf_pos(sb, prec, round_ceiling) + # Upper point nonnegative? + elif sbs >= 0: + a = fzero + negsa = mpf_neg(sa) + if mpf_lt(negsa, sb): + b = mpf_pos(sb, prec, round_ceiling) + else: + b = mpf_pos(negsa, prec, round_ceiling) + # Both negative? + else: + a = mpf_neg(sb, prec, round_floor) + b = mpf_neg(sa, prec, round_ceiling) + return a, b + +# TODO: optimize +def mpi_mul_mpf(s, t, prec): + return mpi_mul(s, (t, t), prec) + +def mpi_div_mpf(s, t, prec): + return mpi_div(s, (t, t), prec) + +def mpi_mul(s, t, prec=0): + sa, sb = s + ta, tb = t + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + tas = mpf_sign(ta) + tbs = mpf_sign(tb) + if sas == sbs == 0: + # Should maybe be undefined + if ta == fninf or tb == finf: + return fninf, finf + return fzero, fzero + if tas == tbs == 0: + # Should maybe be undefined + if sa == fninf or sb == finf: + return fninf, finf + return fzero, fzero + if sas >= 0: + # positive * positive + if tas >= 0: + a = mpf_mul(sa, ta, prec, round_floor) + b = mpf_mul(sb, tb, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # positive * negative + elif tbs <= 0: + a = mpf_mul(sb, ta, prec, round_floor) + b = mpf_mul(sa, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # positive * both signs + else: + a = mpf_mul(sb, ta, prec, round_floor) + b = mpf_mul(sb, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + elif sbs <= 0: + # negative * positive + if tas >= 0: + a = mpf_mul(sa, tb, prec, round_floor) + b = mpf_mul(sb, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # negative * negative + elif tbs <= 0: + a = mpf_mul(sb, tb, prec, round_floor) + b = mpf_mul(sa, ta, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # negative * both signs + else: + a = mpf_mul(sa, tb, prec, round_floor) + b = mpf_mul(sa, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + else: + # General case: perform all cross-multiplications and compare + # Since the multiplications can be done exactly, we need only + # do 4 (instead of 8: two for each rounding mode) + cases = [mpf_mul(sa, ta), mpf_mul(sa, tb), mpf_mul(sb, ta), mpf_mul(sb, tb)] + if fnan in cases: + a, b = (fninf, finf) + else: + a, b = mpf_min_max(cases) + a = mpf_pos(a, prec, round_floor) + b = mpf_pos(b, prec, round_ceiling) + return a, b + +def mpi_square(s, prec=0): + sa, sb = s + if mpf_ge(sa, fzero): + a = mpf_mul(sa, sa, prec, round_floor) + b = mpf_mul(sb, sb, prec, round_ceiling) + elif mpf_le(sb, fzero): + a = mpf_mul(sb, sb, prec, round_floor) + b = mpf_mul(sa, sa, prec, round_ceiling) + else: + sa = mpf_neg(sa) + sa, sb = mpf_min_max([sa, sb]) + a = fzero + b = mpf_mul(sb, sb, prec, round_ceiling) + return a, b + +def mpi_div(s, t, prec): + sa, sb = s + ta, tb = t + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + tas = mpf_sign(ta) + tbs = mpf_sign(tb) + # 0 / X + if sas == sbs == 0: + # 0 / + if (tas < 0 and tbs > 0) or (tas == 0 or tbs == 0): + return fninf, finf + return fzero, fzero + # Denominator contains both negative and positive numbers; + # this should properly be a multi-interval, but the closest + # match is the entire (extended) real line + if tas < 0 and tbs > 0: + return fninf, finf + # Assume denominator to be nonnegative + if tas < 0: + return mpi_div(mpi_neg(s), mpi_neg(t), prec) + # Division by zero + # XXX: make sure all results make sense + if tas == 0: + # Numerator contains both signs? + if sas < 0 and sbs > 0: + return fninf, finf + if tas == tbs: + return fninf, finf + # Numerator positive? + if sas >= 0: + a = mpf_div(sa, tb, prec, round_floor) + b = finf + if sbs <= 0: + a = fninf + b = mpf_div(sb, tb, prec, round_ceiling) + # Division with positive denominator + # We still have to handle nans resulting from inf/0 or inf/inf + else: + # Nonnegative numerator + if sas >= 0: + a = mpf_div(sa, tb, prec, round_floor) + b = mpf_div(sb, ta, prec, round_ceiling) + if a == fnan: a = fzero + if b == fnan: b = finf + # Nonpositive numerator + elif sbs <= 0: + a = mpf_div(sa, ta, prec, round_floor) + b = mpf_div(sb, tb, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = fzero + # Numerator contains both signs? + else: + a = mpf_div(sa, ta, prec, round_floor) + b = mpf_div(sb, ta, prec, round_ceiling) + if a == fnan: a = fninf + if b == fnan: b = finf + return a, b + +def mpi_pi(prec): + a = mpf_pi(prec, round_floor) + b = mpf_pi(prec, round_ceiling) + return a, b + +def mpi_exp(s, prec): + sa, sb = s + # exp is monotonic + a = mpf_exp(sa, prec, round_floor) + b = mpf_exp(sb, prec, round_ceiling) + return a, b + +def mpi_log(s, prec): + sa, sb = s + # log is monotonic + a = mpf_log(sa, prec, round_floor) + b = mpf_log(sb, prec, round_ceiling) + return a, b + +def mpi_sqrt(s, prec): + sa, sb = s + # sqrt is monotonic + a = mpf_sqrt(sa, prec, round_floor) + b = mpf_sqrt(sb, prec, round_ceiling) + return a, b + +def mpi_atan(s, prec): + sa, sb = s + a = mpf_atan(sa, prec, round_floor) + b = mpf_atan(sb, prec, round_ceiling) + return a, b + +def mpi_pow_int(s, n, prec): + sa, sb = s + if n < 0: + return mpi_div((fone, fone), mpi_pow_int(s, -n, prec+20), prec) + if n == 0: + return (fone, fone) + if n == 1: + return s + if n == 2: + return mpi_square(s, prec) + # Odd -- signs are preserved + if n & 1: + a = mpf_pow_int(sa, n, prec, round_floor) + b = mpf_pow_int(sb, n, prec, round_ceiling) + # Even -- important to ensure positivity + else: + sas = mpf_sign(sa) + sbs = mpf_sign(sb) + # Nonnegative? + if sas >= 0: + a = mpf_pow_int(sa, n, prec, round_floor) + b = mpf_pow_int(sb, n, prec, round_ceiling) + # Nonpositive? + elif sbs <= 0: + a = mpf_pow_int(sb, n, prec, round_floor) + b = mpf_pow_int(sa, n, prec, round_ceiling) + # Mixed signs? + else: + a = fzero + # max(-a,b)**n + sa = mpf_neg(sa) + if mpf_ge(sa, sb): + b = mpf_pow_int(sa, n, prec, round_ceiling) + else: + b = mpf_pow_int(sb, n, prec, round_ceiling) + return a, b + +def mpi_pow(s, t, prec): + ta, tb = t + if ta == tb and ta not in (finf, fninf): + if ta == from_int(to_int(ta)): + return mpi_pow_int(s, to_int(ta), prec) + if ta == fhalf: + return mpi_sqrt(s, prec) + u = mpi_log(s, prec + 20) + v = mpi_mul(u, t, prec + 20) + return mpi_exp(v, prec) + +def MIN(x, y): + if mpf_le(x, y): + return x + return y + +def MAX(x, y): + if mpf_ge(x, y): + return x + return y + +def cos_sin_quadrant(x, wp): + sign, man, exp, bc = x + if x == fzero: + return fone, fzero, 0 + # TODO: combine evaluation code to avoid duplicate modulo + c, s = mpf_cos_sin(x, wp) + t, n, wp_ = mod_pi2(man, exp, exp+bc, 15) + if sign: + n = -1-n + return c, s, n + +def mpi_cos_sin(x, prec): + a, b = x + if a == b == fzero: + return (fone, fone), (fzero, fzero) + # Guaranteed to contain both -1 and 1 + if (finf in x) or (fninf in x): + return (fnone, fone), (fnone, fone) + wp = prec + 20 + ca, sa, na = cos_sin_quadrant(a, wp) + cb, sb, nb = cos_sin_quadrant(b, wp) + ca, cb = mpf_min_max([ca, cb]) + sa, sb = mpf_min_max([sa, sb]) + # Both functions are monotonic within one quadrant + if na == nb: + pass + # Guaranteed to contain both -1 and 1 + elif nb - na >= 4: + return (fnone, fone), (fnone, fone) + else: + # cos has maximum between a and b + if na//4 != nb//4: + cb = fone + # cos has minimum + if (na-2)//4 != (nb-2)//4: + ca = fnone + # sin has maximum + if (na-1)//4 != (nb-1)//4: + sb = fone + # sin has minimum + if (na-3)//4 != (nb-3)//4: + sa = fnone + # Perturb to force interval rounding + more = from_man_exp((MPZ_ONE<= 1: + if sign: + return fnone + return fone + return v + ca = finalize(ca, round_floor) + cb = finalize(cb, round_ceiling) + sa = finalize(sa, round_floor) + sb = finalize(sb, round_ceiling) + return (ca,cb), (sa,sb) + +def mpi_cos(x, prec): + return mpi_cos_sin(x, prec)[0] + +def mpi_sin(x, prec): + return mpi_cos_sin(x, prec)[1] + +def mpi_tan(x, prec): + cos, sin = mpi_cos_sin(x, prec+20) + return mpi_div(sin, cos, prec) + +def mpi_cot(x, prec): + cos, sin = mpi_cos_sin(x, prec+20) + return mpi_div(cos, sin, prec) + +def mpi_from_str_a_b(x, y, percent, prec): + wp = prec + 20 + xa = from_str(x, wp, round_floor) + xb = from_str(x, wp, round_ceiling) + #ya = from_str(y, wp, round_floor) + y = from_str(y, wp, round_ceiling) + assert mpf_ge(y, fzero) + if percent: + y = mpf_mul(MAX(mpf_abs(xa), mpf_abs(xb)), y, wp, round_ceiling) + y = mpf_div(y, from_int(100), wp, round_ceiling) + a = mpf_sub(xa, y, prec, round_floor) + b = mpf_add(xb, y, prec, round_ceiling) + return a, b + +def mpi_from_str(s, prec): + """ + Parse an interval number given as a string. + + Allowed forms are + + "-1.23e-27" + Any single decimal floating-point literal. + "a +- b" or "a (b)" + a is the midpoint of the interval and b is the half-width + "a +- b%" or "a (b%)" + a is the midpoint of the interval and the half-width + is b percent of a (`a \times b / 100`). + "[a, b]" + The interval indicated directly. + "x[y,z]e" + x are shared digits, y and z are unequal digits, e is the exponent. + + """ + e = ValueError("Improperly formed interval number '%s'" % s) + s = s.replace(" ", "") + wp = prec + 20 + if "+-" in s: + x, y = s.split("+-") + return mpi_from_str_a_b(x, y, False, prec) + # case 2 + elif "(" in s: + # Don't confuse with a complex number (x,y) + if s[0] == "(" or ")" not in s: + raise e + s = s.replace(")", "") + percent = False + if "%" in s: + if s[-1] != "%": + raise e + percent = True + s = s.replace("%", "") + x, y = s.split("(") + return mpi_from_str_a_b(x, y, percent, prec) + elif "," in s: + if ('[' not in s) or (']' not in s): + raise e + if s[0] == '[': + # case 3 + s = s.replace("[", "") + s = s.replace("]", "") + a, b = s.split(",") + a = from_str(a, prec, round_floor) + b = from_str(b, prec, round_ceiling) + return a, b + else: + # case 4 + x, y = s.split('[') + y, z = y.split(',') + if 'e' in s: + z, e = z.split(']') + else: + z, e = z.rstrip(']'), '' + a = from_str(x+y+e, prec, round_floor) + b = from_str(x+z+e, prec, round_ceiling) + return a, b + else: + a = from_str(s, prec, round_floor) + b = from_str(s, prec, round_ceiling) + return a, b + +def mpi_to_str(x, dps, use_spaces=True, brackets='[]', mode='brackets', error_dps=4, **kwargs): + """ + Convert a mpi interval to a string. + + **Arguments** + + *dps* + decimal places to use for printing + *use_spaces* + use spaces for more readable output, defaults to true + *brackets* + pair of strings (or two-character string) giving left and right brackets + *mode* + mode of display: 'plusminus', 'percent', 'brackets' (default) or 'diff' + *error_dps* + limit the error to *error_dps* digits (mode 'plusminus and 'percent') + + Additional keyword arguments are forwarded to the mpf-to-string conversion + for the components of the output. + + **Examples** + + >>> from mpmath import mpi, mp + >>> mp.dps = 30 + >>> x = mpi(1, 2)._mpi_ + >>> mpi_to_str(x, 2, mode='plusminus') + '1.5 +- 0.5' + >>> mpi_to_str(x, 2, mode='percent') + '1.5 (33.33%)' + >>> mpi_to_str(x, 2, mode='brackets') + '[1.0, 2.0]' + >>> mpi_to_str(x, 2, mode='brackets' , brackets=('<', '>')) + '<1.0, 2.0>' + >>> x = mpi('5.2582327113062393041', '5.2582327113062749951')._mpi_ + >>> mpi_to_str(x, 15, mode='diff') + '5.2582327113062[4, 7]' + >>> mpi_to_str(mpi(0)._mpi_, 2, mode='percent') + '0.0 (0.0%)' + + """ + prec = dps_to_prec(dps) + wp = prec + 20 + a, b = x + mid = mpi_mid(x, prec) + delta = mpi_delta(x, prec) + a_str = to_str(a, dps, **kwargs) + b_str = to_str(b, dps, **kwargs) + mid_str = to_str(mid, dps, **kwargs) + sp = "" + if use_spaces: + sp = " " + br1, br2 = brackets + if mode == 'plusminus': + delta_str = to_str(mpf_shift(delta,-1), dps, **kwargs) + s = mid_str + sp + "+-" + sp + delta_str + elif mode == 'percent': + if mid == fzero: + p = fzero + else: + # p = 100 * delta(x) / (2*mid(x)) + p = mpf_mul(delta, from_int(100)) + p = mpf_div(p, mpf_mul(mid, from_int(2)), wp) + s = mid_str + sp + "(" + to_str(p, error_dps) + "%)" + elif mode == 'brackets': + s = br1 + a_str + "," + sp + b_str + br2 + elif mode == 'diff': + # use more digits if str(x.a) and str(x.b) are equal + if a_str == b_str: + a_str = to_str(a, dps+3, **kwargs) + b_str = to_str(b, dps+3, **kwargs) + # separate mantissa and exponent + a = a_str.split('e') + if len(a) == 1: + a.append('') + b = b_str.split('e') + if len(b) == 1: + b.append('') + if a[1] == b[1]: + if a[0] != b[0]: + for i in xrange(len(a[0]) + 1): + if a[0][i] != b[0][i]: + break + s = (a[0][:i] + br1 + a[0][i:] + ',' + sp + b[0][i:] + br2 + + 'e'*min(len(a[1]), 1) + a[1]) + else: # no difference + s = a[0] + br1 + br2 + 'e'*min(len(a[1]), 1) + a[1] + else: + s = br1 + 'e'.join(a) + ',' + sp + 'e'.join(b) + br2 + else: + raise ValueError("'%s' is unknown mode for printing mpi" % mode) + return s + +def mpci_add(x, y, prec): + a, b = x + c, d = y + return mpi_add(a, c, prec), mpi_add(b, d, prec) + +def mpci_sub(x, y, prec): + a, b = x + c, d = y + return mpi_sub(a, c, prec), mpi_sub(b, d, prec) + +def mpci_neg(x, prec=0): + a, b = x + return mpi_neg(a, prec), mpi_neg(b, prec) + +def mpci_pos(x, prec): + a, b = x + return mpi_pos(a, prec), mpi_pos(b, prec) + +def mpci_mul(x, y, prec): + # TODO: optimize for real/imag cases + a, b = x + c, d = y + r1 = mpi_mul(a,c) + r2 = mpi_mul(b,d) + re = mpi_sub(r1,r2,prec) + i1 = mpi_mul(a,d) + i2 = mpi_mul(b,c) + im = mpi_add(i1,i2,prec) + return re, im + +def mpci_div(x, y, prec): + # TODO: optimize for real/imag cases + a, b = x + c, d = y + wp = prec+20 + m1 = mpi_square(c) + m2 = mpi_square(d) + m = mpi_add(m1,m2,wp) + re = mpi_add(mpi_mul(a,c), mpi_mul(b,d), wp) + im = mpi_sub(mpi_mul(b,c), mpi_mul(a,d), wp) + re = mpi_div(re, m, prec) + im = mpi_div(im, m, prec) + return re, im + +def mpci_exp(x, prec): + a, b = x + wp = prec+20 + r = mpi_exp(a, wp) + c, s = mpi_cos_sin(b, wp) + a = mpi_mul(r, c, prec) + b = mpi_mul(r, s, prec) + return a, b + +def mpi_shift(x, n): + a, b = x + return mpf_shift(a,n), mpf_shift(b,n) + +def mpi_cosh_sinh(x, prec): + # TODO: accuracy for small x + wp = prec+20 + e1 = mpi_exp(x, wp) + e2 = mpi_div(mpi_one, e1, wp) + c = mpi_add(e1, e2, prec) + s = mpi_sub(e1, e2, prec) + c = mpi_shift(c, -1) + s = mpi_shift(s, -1) + return c, s + +def mpci_cos(x, prec): + a, b = x + wp = prec+10 + c, s = mpi_cos_sin(a, wp) + ch, sh = mpi_cosh_sinh(b, wp) + re = mpi_mul(c, ch, prec) + im = mpi_mul(s, sh, prec) + return re, mpi_neg(im) + +def mpci_sin(x, prec): + a, b = x + wp = prec+10 + c, s = mpi_cos_sin(a, wp) + ch, sh = mpi_cosh_sinh(b, wp) + re = mpi_mul(s, ch, prec) + im = mpi_mul(c, sh, prec) + return re, im + +def mpci_abs(x, prec): + a, b = x + if a == mpi_zero: + return mpi_abs(b) + if b == mpi_zero: + return mpi_abs(a) + # Important: nonnegative + a = mpi_square(a) + b = mpi_square(b) + t = mpi_add(a, b, prec+20) + return mpi_sqrt(t, prec) + +def mpi_atan2(y, x, prec): + ya, yb = y + xa, xb = x + # Constrained to the real line + if ya == yb == fzero: + if mpf_ge(xa, fzero): + return mpi_zero + return mpi_pi(prec) + # Right half-plane + if mpf_ge(xa, fzero): + if mpf_ge(ya, fzero): + a = mpf_atan2(ya, xb, prec, round_floor) + else: + a = mpf_atan2(ya, xa, prec, round_floor) + if mpf_ge(yb, fzero): + b = mpf_atan2(yb, xa, prec, round_ceiling) + else: + b = mpf_atan2(yb, xb, prec, round_ceiling) + # Upper half-plane + elif mpf_ge(ya, fzero): + b = mpf_atan2(ya, xa, prec, round_ceiling) + if mpf_le(xb, fzero): + a = mpf_atan2(yb, xb, prec, round_floor) + else: + a = mpf_atan2(ya, xb, prec, round_floor) + # Lower half-plane + elif mpf_le(yb, fzero): + a = mpf_atan2(yb, xa, prec, round_floor) + if mpf_le(xb, fzero): + b = mpf_atan2(ya, xb, prec, round_ceiling) + else: + b = mpf_atan2(yb, xb, prec, round_ceiling) + # Covering the origin + else: + b = mpf_pi(prec, round_ceiling) + a = mpf_neg(b) + return a, b + +def mpci_arg(z, prec): + x, y = z + return mpi_atan2(y, x, prec) + +def mpci_log(z, prec): + x, y = z + re = mpi_log(mpci_abs(z, prec+20), prec) + im = mpci_arg(z, prec) + return re, im + +def mpci_pow(x, y, prec): + # TODO: recognize/speed up real cases, integer y + yre, yim = y + if yim == mpi_zero: + ya, yb = yre + if ya == yb: + sign, man, exp, bc = yb + if man and exp >= 0: + return mpci_pow_int(x, (-1)**sign * int(man<>= 1 + return mpci_pos(result, prec) + +gamma_min_a = from_float(1.46163214496) +gamma_min_b = from_float(1.46163214497) +gamma_min = (gamma_min_a, gamma_min_b) +gamma_mono_imag_a = from_float(-1.1) +gamma_mono_imag_b = from_float(1.1) + +def mpi_overlap(x, y): + a, b = x + c, d = y + if mpf_lt(d, a): return False + if mpf_gt(c, b): return False + return True + +# type = 0 -- gamma +# type = 1 -- factorial +# type = 2 -- 1/gamma +# type = 3 -- log-gamma + +def mpi_gamma(z, prec, type=0): + a, b = z + wp = prec+20 + + if type == 1: + return mpi_gamma(mpi_add(z, mpi_one, wp), prec, 0) + + # increasing + if mpf_gt(a, gamma_min_b): + if type == 0: + c = mpf_gamma(a, prec, round_floor) + d = mpf_gamma(b, prec, round_ceiling) + elif type == 2: + c = mpf_rgamma(b, prec, round_floor) + d = mpf_rgamma(a, prec, round_ceiling) + elif type == 3: + c = mpf_loggamma(a, prec, round_floor) + d = mpf_loggamma(b, prec, round_ceiling) + # decreasing + elif mpf_gt(a, fzero) and mpf_lt(b, gamma_min_a): + if type == 0: + c = mpf_gamma(b, prec, round_floor) + d = mpf_gamma(a, prec, round_ceiling) + elif type == 2: + c = mpf_rgamma(a, prec, round_floor) + d = mpf_rgamma(b, prec, round_ceiling) + elif type == 3: + c = mpf_loggamma(b, prec, round_floor) + d = mpf_loggamma(a, prec, round_ceiling) + else: + # TODO: reflection formula + znew = mpi_add(z, mpi_one, wp) + if type == 0: return mpi_div(mpi_gamma(znew, prec+2, 0), z, prec) + if type == 2: return mpi_mul(mpi_gamma(znew, prec+2, 2), z, prec) + if type == 3: return mpi_sub(mpi_gamma(znew, prec+2, 3), mpi_log(z, prec+2), prec) + return c, d + +def mpci_gamma(z, prec, type=0): + (a1,a2), (b1,b2) = z + + # Real case + if b1 == b2 == fzero and (type != 3 or mpf_gt(a1,fzero)): + return mpi_gamma(z, prec, type), mpi_zero + + # Estimate precision + wp = prec+20 + if type != 3: + amag = a2[2]+a2[3] + bmag = b2[2]+b2[3] + if a2 != fzero: + mag = max(amag, bmag) + else: + mag = bmag + an = abs(to_int(a2)) + bn = abs(to_int(b2)) + absn = max(an, bn) + gamma_size = max(0,absn*mag) + wp += bitcount(gamma_size) + + # Assume type != 1 + if type == 1: + (a1,a2) = mpi_add((a1,a2), mpi_one, wp); z = (a1,a2), (b1,b2) + type = 0 + + # Avoid non-monotonic region near the negative real axis + if mpf_lt(a1, gamma_min_b): + if mpi_overlap((b1,b2), (gamma_mono_imag_a, gamma_mono_imag_b)): + # TODO: reflection formula + #if mpf_lt(a2, mpf_shift(fone,-1)): + # znew = mpci_sub((mpi_one,mpi_zero),z,wp) + # ... + # Recurrence: + # gamma(z) = gamma(z+1)/z + znew = mpi_add((a1,a2), mpi_one, wp), (b1,b2) + if type == 0: return mpci_div(mpci_gamma(znew, prec+2, 0), z, prec) + if type == 2: return mpci_mul(mpci_gamma(znew, prec+2, 2), z, prec) + if type == 3: return mpci_sub(mpci_gamma(znew, prec+2, 3), mpci_log(z,prec+2), prec) + + # Use monotonicity (except for a small region close to the + # origin and near poles) + # upper half-plane + if mpf_ge(b1, fzero): + minre = mpc_loggamma((a1,b2), wp, round_floor) + maxre = mpc_loggamma((a2,b1), wp, round_ceiling) + minim = mpc_loggamma((a1,b1), wp, round_floor) + maxim = mpc_loggamma((a2,b2), wp, round_ceiling) + # lower half-plane + elif mpf_le(b2, fzero): + minre = mpc_loggamma((a1,b1), wp, round_floor) + maxre = mpc_loggamma((a2,b2), wp, round_ceiling) + minim = mpc_loggamma((a2,b1), wp, round_floor) + maxim = mpc_loggamma((a1,b2), wp, round_ceiling) + # crosses real axis + else: + maxre = mpc_loggamma((a2,fzero), wp, round_ceiling) + # stretches more into the lower half-plane + if mpf_gt(mpf_neg(b1), b2): + minre = mpc_loggamma((a1,b1), wp, round_ceiling) + else: + minre = mpc_loggamma((a1,b2), wp, round_ceiling) + minim = mpc_loggamma((a2,b1), wp, round_floor) + maxim = mpc_loggamma((a2,b2), wp, round_floor) + + w = (minre[0], maxre[0]), (minim[1], maxim[1]) + if type == 3: + return mpi_pos(w[0], prec), mpi_pos(w[1], prec) + if type == 2: + w = mpci_neg(w) + return mpci_exp(w, prec) + +def mpi_loggamma(z, prec): return mpi_gamma(z, prec, type=3) +def mpci_loggamma(z, prec): return mpci_gamma(z, prec, type=3) + +def mpi_rgamma(z, prec): return mpi_gamma(z, prec, type=2) +def mpci_rgamma(z, prec): return mpci_gamma(z, prec, type=2) + +def mpi_factorial(z, prec): return mpi_gamma(z, prec, type=1) +def mpci_factorial(z, prec): return mpci_gamma(z, prec, type=1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..293697b9fcf8bd82d58ac4ff45acd73fadac82f9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__init__.py @@ -0,0 +1,2 @@ +from . import eigen # to set methods +from . import eigen_symmetric # to set methods diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9105288f07b043e836db1772f4c758585fc657b1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/calculus.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/calculus.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7312a59761917d6470db8cd88c2a64d757c44cb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/calculus.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3aa3c837c5b0c279cb9a24337384e07d5f978c36 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen_symmetric.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen_symmetric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..184e06a5b227db19588b66170c9b923a57b2835c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/eigen_symmetric.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/linalg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/linalg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70e003c6cfc0fcfb7eee70046992af9d836bba96 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/linalg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/matrices.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/matrices.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebc8943d23bc4e4661f1938e79ff8688a05ad000 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/__pycache__/matrices.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/calculus.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/calculus.py new file mode 100644 index 0000000000000000000000000000000000000000..7fae2a7a9a29898241ed41810331b480ff70798f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/calculus.py @@ -0,0 +1,531 @@ +from ..libmp.backend import xrange + +# TODO: should use diagonalization-based algorithms + +class MatrixCalculusMethods(object): + + def _exp_pade(ctx, a): + """ + Exponential of a matrix using Pade approximants. + + See G. H. Golub, C. F. van Loan 'Matrix Computations', + third Ed., page 572 + + TODO: + - find a good estimate for q + - reduce the number of matrix multiplications to improve + performance + """ + def eps_pade(p): + return ctx.mpf(2)**(3-2*p) * \ + ctx.factorial(p)**2/(ctx.factorial(2*p)**2 * (2*p + 1)) + q = 4 + extraq = 8 + while 1: + if eps_pade(q) < ctx.eps: + break + q += 1 + q += extraq + j = int(max(1, ctx.mag(ctx.mnorm(a,'inf')))) + extra = q + prec = ctx.prec + ctx.dps += extra + 3 + try: + a = a/2**j + na = a.rows + den = ctx.eye(na) + num = ctx.eye(na) + x = ctx.eye(na) + c = ctx.mpf(1) + for k in range(1, q+1): + c *= ctx.mpf(q - k + 1)/((2*q - k + 1) * k) + x = a*x + cx = c*x + num += cx + den += (-1)**k * cx + f = ctx.lu_solve_mat(den, num) + for k in range(j): + f = f*f + finally: + ctx.prec = prec + return f*1 + + def expm(ctx, A, method='taylor'): + r""" + Computes the matrix exponential of a square matrix `A`, which is defined + by the power series + + .. math :: + + \exp(A) = I + A + \frac{A^2}{2!} + \frac{A^3}{3!} + \ldots + + With method='taylor', the matrix exponential is computed + using the Taylor series. With method='pade', Pade approximants + are used instead. + + **Examples** + + Basic examples:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> expm(zeros(3)) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + >>> expm(eye(3)) + [2.71828182845905 0.0 0.0] + [ 0.0 2.71828182845905 0.0] + [ 0.0 0.0 2.71828182845905] + >>> expm([[1,1,0],[1,0,1],[0,1,0]]) + [ 3.86814500615414 2.26812870852145 0.841130841230196] + [ 2.26812870852145 2.44114713886289 1.42699786729125] + [0.841130841230196 1.42699786729125 1.6000162976327] + >>> expm([[1,1,0],[1,0,1],[0,1,0]], method='pade') + [ 3.86814500615414 2.26812870852145 0.841130841230196] + [ 2.26812870852145 2.44114713886289 1.42699786729125] + [0.841130841230196 1.42699786729125 1.6000162976327] + >>> expm([[1+j, 0], [1+j,1]]) + [(1.46869393991589 + 2.28735528717884j) 0.0] + [ (1.03776739863568 + 3.536943175722j) (2.71828182845905 + 0.0j)] + + Matrices with large entries are allowed:: + + >>> expm(matrix([[1,2],[2,3]])**25) + [5.65024064048415e+2050488462815550 9.14228140091932e+2050488462815550] + [9.14228140091932e+2050488462815550 1.47925220414035e+2050488462815551] + + The identity `\exp(A+B) = \exp(A) \exp(B)` does not hold for + noncommuting matrices:: + + >>> A = hilbert(3) + >>> B = A + eye(3) + >>> chop(mnorm(A*B - B*A)) + 0.0 + >>> chop(mnorm(expm(A+B) - expm(A)*expm(B))) + 0.0 + >>> B = A + ones(3) + >>> mnorm(A*B - B*A) + 1.8 + >>> mnorm(expm(A+B) - expm(A)*expm(B)) + 42.0927851137247 + + """ + if method == 'pade': + prec = ctx.prec + try: + A = ctx.matrix(A) + ctx.prec += 2*A.rows + res = ctx._exp_pade(A) + finally: + ctx.prec = prec + return res + A = ctx.matrix(A) + prec = ctx.prec + j = int(max(1, ctx.mag(ctx.mnorm(A,'inf')))) + j += int(0.5*prec**0.5) + try: + ctx.prec += 10 + 2*j + tol = +ctx.eps + A = A/2**j + T = A + Y = A**0 + A + k = 2 + while 1: + T *= A * (1/ctx.mpf(k)) + if ctx.mnorm(T, 'inf') < tol: + break + Y += T + k += 1 + for k in xrange(j): + Y = Y*Y + finally: + ctx.prec = prec + Y *= 1 + return Y + + def cosm(ctx, A): + r""" + Gives the cosine of a square matrix `A`, defined in analogy + with the matrix exponential. + + Examples:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> X = eye(3) + >>> cosm(X) + [0.54030230586814 0.0 0.0] + [ 0.0 0.54030230586814 0.0] + [ 0.0 0.0 0.54030230586814] + >>> X = hilbert(3) + >>> cosm(X) + [ 0.424403834569555 -0.316643413047167 -0.221474945949293] + [-0.316643413047167 0.820646708837824 -0.127183694770039] + [-0.221474945949293 -0.127183694770039 0.909236687217541] + >>> X = matrix([[1+j,-2],[0,-j]]) + >>> cosm(X) + [(0.833730025131149 - 0.988897705762865j) (1.07485840848393 - 0.17192140544213j)] + [ 0.0 (1.54308063481524 + 0.0j)] + """ + B = 0.5 * (ctx.expm(A*ctx.j) + ctx.expm(A*(-ctx.j))) + if not sum(A.apply(ctx.im).apply(abs)): + B = B.apply(ctx.re) + return B + + def sinm(ctx, A): + r""" + Gives the sine of a square matrix `A`, defined in analogy + with the matrix exponential. + + Examples:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> X = eye(3) + >>> sinm(X) + [0.841470984807897 0.0 0.0] + [ 0.0 0.841470984807897 0.0] + [ 0.0 0.0 0.841470984807897] + >>> X = hilbert(3) + >>> sinm(X) + [0.711608512150994 0.339783913247439 0.220742837314741] + [0.339783913247439 0.244113865695532 0.187231271174372] + [0.220742837314741 0.187231271174372 0.155816730769635] + >>> X = matrix([[1+j,-2],[0,-j]]) + >>> sinm(X) + [(1.29845758141598 + 0.634963914784736j) (-1.96751511930922 + 0.314700021761367j)] + [ 0.0 (0.0 - 1.1752011936438j)] + """ + B = (-0.5j) * (ctx.expm(A*ctx.j) - ctx.expm(A*(-ctx.j))) + if not sum(A.apply(ctx.im).apply(abs)): + B = B.apply(ctx.re) + return B + + def _sqrtm_rot(ctx, A, _may_rotate): + # If the iteration fails to converge, cheat by performing + # a rotation by a complex number + u = ctx.j**0.3 + return ctx.sqrtm(u*A, _may_rotate) / ctx.sqrt(u) + + def sqrtm(ctx, A, _may_rotate=2): + r""" + Computes a square root of the square matrix `A`, i.e. returns + a matrix `B = A^{1/2}` such that `B^2 = A`. The square root + of a matrix, if it exists, is not unique. + + **Examples** + + Square roots of some simple matrices:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> sqrtm([[1,0], [0,1]]) + [1.0 0.0] + [0.0 1.0] + >>> sqrtm([[0,0], [0,0]]) + [0.0 0.0] + [0.0 0.0] + >>> sqrtm([[2,0],[0,1]]) + [1.4142135623731 0.0] + [ 0.0 1.0] + >>> sqrtm([[1,1],[1,0]]) + [ (0.920442065259926 - 0.21728689675164j) (0.568864481005783 + 0.351577584254143j)] + [(0.568864481005783 + 0.351577584254143j) (0.351577584254143 - 0.568864481005783j)] + >>> sqrtm([[1,0],[0,1]]) + [1.0 0.0] + [0.0 1.0] + >>> sqrtm([[-1,0],[0,1]]) + [(0.0 - 1.0j) 0.0] + [ 0.0 (1.0 + 0.0j)] + >>> sqrtm([[j,0],[0,j]]) + [(0.707106781186547 + 0.707106781186547j) 0.0] + [ 0.0 (0.707106781186547 + 0.707106781186547j)] + + A square root of a rotation matrix, giving the corresponding + half-angle rotation matrix:: + + >>> t1 = 0.75 + >>> t2 = t1 * 0.5 + >>> A1 = matrix([[cos(t1), -sin(t1)], [sin(t1), cos(t1)]]) + >>> A2 = matrix([[cos(t2), -sin(t2)], [sin(t2), cos(t2)]]) + >>> sqrtm(A1) + [0.930507621912314 -0.366272529086048] + [0.366272529086048 0.930507621912314] + >>> A2 + [0.930507621912314 -0.366272529086048] + [0.366272529086048 0.930507621912314] + + The identity `(A^2)^{1/2} = A` does not necessarily hold:: + + >>> A = matrix([[4,1,4],[7,8,9],[10,2,11]]) + >>> sqrtm(A**2) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> sqrtm(A)**2 + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> A = matrix([[-4,1,4],[7,-8,9],[10,2,11]]) + >>> sqrtm(A**2) + [ 7.43715112194995 -0.324127569985474 1.8481718827526] + [-0.251549715716942 9.32699765900402 2.48221180985147] + [ 4.11609388833616 0.775751877098258 13.017955697342] + >>> chop(sqrtm(A)**2) + [-4.0 1.0 4.0] + [ 7.0 -8.0 9.0] + [10.0 2.0 11.0] + + For some matrices, a square root does not exist:: + + >>> sqrtm([[0,1], [0,0]]) + Traceback (most recent call last): + ... + ZeroDivisionError: matrix is numerically singular + + Two examples from the documentation for Matlab's ``sqrtm``:: + + >>> mp.dps = 15; mp.pretty = True + >>> sqrtm([[7,10],[15,22]]) + [1.56669890360128 1.74077655955698] + [2.61116483933547 4.17786374293675] + >>> + >>> X = matrix(\ + ... [[5,-4,1,0,0], + ... [-4,6,-4,1,0], + ... [1,-4,6,-4,1], + ... [0,1,-4,6,-4], + ... [0,0,1,-4,5]]) + >>> Y = matrix(\ + ... [[2,-1,-0,-0,-0], + ... [-1,2,-1,0,-0], + ... [0,-1,2,-1,0], + ... [-0,0,-1,2,-1], + ... [-0,-0,-0,-1,2]]) + >>> mnorm(sqrtm(X) - Y) + 4.53155328326114e-19 + + """ + A = ctx.matrix(A) + # Trivial + if A*0 == A: + return A + prec = ctx.prec + if _may_rotate: + d = ctx.det(A) + if abs(ctx.im(d)) < 16*ctx.eps and ctx.re(d) < 0: + return ctx._sqrtm_rot(A, _may_rotate-1) + try: + ctx.prec += 10 + tol = ctx.eps * 128 + Y = A + Z = I = A**0 + k = 0 + # Denman-Beavers iteration + while 1: + Yprev = Y + try: + Y, Z = 0.5*(Y+ctx.inverse(Z)), 0.5*(Z+ctx.inverse(Y)) + except ZeroDivisionError: + if _may_rotate: + Y = ctx._sqrtm_rot(A, _may_rotate-1) + break + else: + raise + mag1 = ctx.mnorm(Y-Yprev, 'inf') + mag2 = ctx.mnorm(Y, 'inf') + if mag1 <= mag2*tol: + break + if _may_rotate and k > 6 and not mag1 < mag2 * 0.001: + return ctx._sqrtm_rot(A, _may_rotate-1) + k += 1 + if k > ctx.prec: + raise ctx.NoConvergence + finally: + ctx.prec = prec + Y *= 1 + return Y + + def logm(ctx, A): + r""" + Computes a logarithm of the square matrix `A`, i.e. returns + a matrix `B = \log(A)` such that `\exp(B) = A`. The logarithm + of a matrix, if it exists, is not unique. + + **Examples** + + Logarithms of some simple matrices:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> X = eye(3) + >>> logm(X) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + >>> logm(2*X) + [0.693147180559945 0.0 0.0] + [ 0.0 0.693147180559945 0.0] + [ 0.0 0.0 0.693147180559945] + >>> logm(expm(X)) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + + A logarithm of a complex matrix:: + + >>> X = matrix([[2+j, 1, 3], [1-j, 1-2*j, 1], [-4, -5, j]]) + >>> B = logm(X) + >>> nprint(B) + [ (0.808757 + 0.107759j) (2.20752 + 0.202762j) (1.07376 - 0.773874j)] + [ (0.905709 - 0.107795j) (0.0287395 - 0.824993j) (0.111619 + 0.514272j)] + [(-0.930151 + 0.399512j) (-2.06266 - 0.674397j) (0.791552 + 0.519839j)] + >>> chop(expm(B)) + [(2.0 + 1.0j) 1.0 3.0] + [(1.0 - 1.0j) (1.0 - 2.0j) 1.0] + [ -4.0 -5.0 (0.0 + 1.0j)] + + A matrix `X` close to the identity matrix, for which + `\log(\exp(X)) = \exp(\log(X)) = X` holds:: + + >>> X = eye(3) + hilbert(3)/4 + >>> X + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + >>> logm(expm(X)) + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + >>> expm(logm(X)) + [ 1.25 0.125 0.0833333333333333] + [ 0.125 1.08333333333333 0.0625] + [0.0833333333333333 0.0625 1.05] + + A logarithm of a rotation matrix, giving back the angle of + the rotation:: + + >>> t = 3.7 + >>> A = matrix([[cos(t),sin(t)],[-sin(t),cos(t)]]) + >>> chop(logm(A)) + [ 0.0 -2.58318530717959] + [2.58318530717959 0.0] + >>> (2*pi-t) + 2.58318530717959 + + For some matrices, a logarithm does not exist:: + + >>> logm([[1,0], [0,0]]) + Traceback (most recent call last): + ... + ZeroDivisionError: matrix is numerically singular + + Logarithm of a matrix with large entries:: + + >>> logm(hilbert(3) * 10**20).apply(re) + [ 45.5597513593433 1.27721006042799 0.317662687717978] + [ 1.27721006042799 42.5222778973542 2.24003708791604] + [0.317662687717978 2.24003708791604 42.395212822267] + + """ + A = ctx.matrix(A) + prec = ctx.prec + try: + ctx.prec += 10 + tol = ctx.eps * 128 + I = A**0 + B = A + n = 0 + while 1: + B = ctx.sqrtm(B) + n += 1 + if ctx.mnorm(B-I, 'inf') < 0.125: + break + T = X = B-I + L = X*0 + k = 1 + while 1: + if k & 1: + L += T / k + else: + L -= T / k + T *= X + if ctx.mnorm(T, 'inf') < tol: + break + k += 1 + if k > ctx.prec: + raise ctx.NoConvergence + finally: + ctx.prec = prec + L *= 2**n + return L + + def powm(ctx, A, r): + r""" + Computes `A^r = \exp(A \log r)` for a matrix `A` and complex + number `r`. + + **Examples** + + Powers and inverse powers of a matrix:: + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = True + >>> A = matrix([[4,1,4],[7,8,9],[10,2,11]]) + >>> powm(A, 2) + [ 63.0 20.0 69.0] + [174.0 89.0 199.0] + [164.0 48.0 179.0] + >>> chop(powm(powm(A, 4), 1/4.)) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> powm(extraprec(20)(powm)(A, -4), -1/4.) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> chop(powm(powm(A, 1+0.5j), 1/(1+0.5j))) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + >>> powm(extraprec(5)(powm)(A, -1.5), -1/(1.5)) + [ 4.0 1.0 4.0] + [ 7.0 8.0 9.0] + [10.0 2.0 11.0] + + A Fibonacci-generating matrix:: + + >>> powm([[1,1],[1,0]], 10) + [89.0 55.0] + [55.0 34.0] + >>> fib(10) + 55.0 + >>> powm([[1,1],[1,0]], 6.5) + [(16.5166626964253 - 0.0121089837381789j) (10.2078589271083 + 0.0195927472575932j)] + [(10.2078589271083 + 0.0195927472575932j) (6.30880376931698 - 0.0317017309957721j)] + >>> (phi**6.5 - (1-phi)**6.5)/sqrt(5) + (10.2078589271083 - 0.0195927472575932j) + >>> powm([[1,1],[1,0]], 6.2) + [ (14.3076953002666 - 0.008222855781077j) (8.81733464837593 + 0.0133048601383712j)] + [(8.81733464837593 + 0.0133048601383712j) (5.49036065189071 - 0.0215277159194482j)] + >>> (phi**6.2 - (1-phi)**6.2)/sqrt(5) + (8.81733464837593 - 0.0133048601383712j) + + """ + A = ctx.matrix(A) + r = ctx.convert(r) + prec = ctx.prec + try: + ctx.prec += 10 + if ctx.isint(r): + v = A ** int(r) + elif ctx.isint(r*2): + y = int(r*2) + v = ctx.sqrtm(A) ** y + else: + v = ctx.expm(r*ctx.logm(A)) + finally: + ctx.prec = prec + v *= 1 + return v diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen.py new file mode 100644 index 0000000000000000000000000000000000000000..885d604203195b695183329acc637de91aeaf5ea --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen.py @@ -0,0 +1,877 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +################################################################################################## +# module for the eigenvalue problem +# Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) +# +# todo: +# - implement balancing +# - agressive early deflation +# +################################################################################################## + +""" +The eigenvalue problem +---------------------- + +This file contains routines for the eigenvalue problem. + +high level routines: + + hessenberg : reduction of a real or complex square matrix to upper Hessenberg form + schur : reduction of a real or complex square matrix to upper Schur form + eig : eigenvalues and eigenvectors of a real or complex square matrix + +low level routines: + + hessenberg_reduce_0 : reduction of a real or complex square matrix to upper Hessenberg form + hessenberg_reduce_1 : auxiliary routine to hessenberg_reduce_0 + qr_step : a single implicitly shifted QR step for an upper Hessenberg matrix + hessenberg_qr : Schur decomposition of an upper Hessenberg matrix + eig_tr_r : right eigenvectors of an upper triangular matrix + eig_tr_l : left eigenvectors of an upper triangular matrix +""" + +from ..libmp.backend import xrange + +class Eigen(object): + pass + +def defun(f): + setattr(Eigen, f.__name__, f) + return f + +def hessenberg_reduce_0(ctx, A, T): + """ + This routine computes the (upper) Hessenberg decomposition of a square matrix A. + Given A, an unitary matrix Q is calculated such that + + Q' A Q = H and Q' Q = Q Q' = 1 + + where H is an upper Hessenberg matrix, meaning that it only contains zeros + below the first subdiagonal. Here ' denotes the hermitian transpose (i.e. + transposition and conjugation). + + parameters: + A (input/output) On input, A contains the square matrix A of + dimension (n,n). On output, A contains a compressed representation + of Q and H. + T (output) An array of length n containing the first elements of + the Householder reflectors. + """ + + # internally we work with householder reflections from the right. + # let u be a row vector (i.e. u[i]=A[i,:i]). then + # Q is build up by reflectors of the type (1-v'v) where v is a suitable + # modification of u. these reflectors are applyed to A from the right. + # because we work with reflectors from the right we have to start with + # the bottom row of A and work then upwards (this corresponds to + # some kind of RQ decomposition). + # the first part of the vectors v (i.e. A[i,:(i-1)]) are stored as row vectors + # in the lower left part of A (excluding the diagonal and subdiagonal). + # the last entry of v is stored in T. + # the upper right part of A (including diagonal and subdiagonal) becomes H. + + + n = A.rows + if n <= 2: return + + for i in xrange(n-1, 1, -1): + + # scale the vector + + scale = 0 + for k in xrange(0, i): + scale += abs(ctx.re(A[i,k])) + abs(ctx.im(A[i,k])) + + scale_inv = 0 + if scale != 0: + scale_inv = 1 / scale + + if scale == 0 or ctx.isinf(scale_inv): + # sadly there are floating point numbers not equal to zero whose reciprocal is infinity + T[i] = 0 + A[i,i-1] = 0 + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in xrange(0, i): + A[i,k] *= scale_inv + rr = ctx.re(A[i,k]) + ii = ctx.im(A[i,k]) + H += rr * rr + ii * ii + + F = A[i,i-1] + f = abs(F) + G = ctx.sqrt(H) + A[i,i-1] = - G * scale + + if f == 0: + T[i] = G + else: + ff = F / f + T[i] = F + G * ff + A[i,i-1] *= ff + + H += G * f + H = 1 / ctx.sqrt(H) + + T[i] *= H + for k in xrange(0, i - 1): + A[i,k] *= H + + for j in xrange(0, i): + # apply housholder transformation (from right) + + G = ctx.conj(T[i]) * A[j,i-1] + for k in xrange(0, i-1): + G += ctx.conj(A[i,k]) * A[j,k] + + A[j,i-1] -= G * T[i] + for k in xrange(0, i-1): + A[j,k] -= G * A[i,k] + + for j in xrange(0, n): + # apply housholder transformation (from left) + + G = T[i] * A[i-1,j] + for k in xrange(0, i-1): + G += A[i,k] * A[k,j] + + A[i-1,j] -= G * ctx.conj(T[i]) + for k in xrange(0, i-1): + A[k,j] -= G * ctx.conj(A[i,k]) + + + +def hessenberg_reduce_1(ctx, A, T): + """ + This routine forms the unitary matrix Q described in hessenberg_reduce_0. + + parameters: + A (input/output) On input, A is the same matrix as delivered by + hessenberg_reduce_0. On output, A is set to Q. + + T (input) On input, T is the same array as delivered by hessenberg_reduce_0. + """ + + n = A.rows + + if n == 1: + A[0,0] = 1 + return + + A[0,0] = A[1,1] = 1 + A[0,1] = A[1,0] = 0 + + for i in xrange(2, n): + if T[i] != 0: + + for j in xrange(0, i): + G = T[i] * A[i-1,j] + for k in xrange(0, i-1): + G += A[i,k] * A[k,j] + + A[i-1,j] -= G * ctx.conj(T[i]) + for k in xrange(0, i-1): + A[k,j] -= G * ctx.conj(A[i,k]) + + A[i,i] = 1 + for j in xrange(0, i): + A[j,i] = A[i,j] = 0 + + + +@defun +def hessenberg(ctx, A, overwrite_a = False): + """ + This routine computes the Hessenberg decomposition of a square matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = H and Q' Q = Q Q' = 1 + + where H is an upper right Hessenberg matrix. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). + + input: + A : a real or complex square matrix + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + Q : an unitary matrix + H : an upper right Hessenberg matrix + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> Q, H = mp.hessenberg(A) + >>> mp.nprint(H, 3) # doctest:+SKIP + [ 3.15 2.23 4.44] + [-0.769 4.85 3.05] + [ 0.0 3.61 7.0] + >>> print(mp.chop(A - Q * H * Q.transpose_conj())) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + return value: (Q, H) + """ + + n = A.rows + + if n == 1: + return (ctx.matrix([[1]]), A) + + if not overwrite_a: + A = A.copy() + + T = ctx.matrix(n, 1) + + hessenberg_reduce_0(ctx, A, T) + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + + for x in xrange(n): + for y in xrange(x+2, n): + A[y,x] = 0 + + return Q, A + + +########################################################################### + + +def qr_step(ctx, n0, n1, A, Q, shift): + """ + This subroutine executes a single implicitly shifted QR step applied to an + upper Hessenberg matrix A. Given A and shift as input, first an QR + decomposition is calculated: + + Q R = A - shift * 1 . + + The output is then following matrix: + + R Q + shift * 1 + + parameters: + n0, n1 (input) Two integers which specify the submatrix A[n0:n1,n0:n1] + on which this subroutine operators. The subdiagonal elements + to the left and below this submatrix must be deflated (i.e. zero). + following restriction is imposed: n1>=n0+2 + A (input/output) On input, A is an upper Hessenberg matrix. + On output, A is replaced by "R Q + shift * 1" + Q (input/output) The parameter Q is multiplied by the unitary matrix + Q arising from the QR decomposition. Q can also be false, in which + case the unitary matrix Q is not computated. + shift (input) a complex number specifying the shift. idealy close to an + eigenvalue of the bottemmost part of the submatrix A[n0:n1,n0:n1]. + + references: + Stoer, Bulirsch - Introduction to Numerical Analysis. + Kresser : Numerical Methods for General and Structured Eigenvalue Problems + """ + + # implicitly shifted and bulge chasing is explained at p.398/399 in "Stoer, Bulirsch - Introduction to Numerical Analysis" + # for bulge chasing see also "Watkins - The Matrix Eigenvalue Problem" sec.4.5,p.173 + + # the Givens rotation we used is determined as follows: let c,s be two complex + # numbers. then we have following relation: + # + # v = sqrt(|c|^2 + |s|^2) + # + # 1/v [ c~ s~] [c] = [v] + # [-s c ] [s] [0] + # + # the matrix on the left is our Givens rotation. + + n = A.rows + + # first step + + # calculate givens rotation + c = A[n0 ,n0] - shift + s = A[n0+1,n0] + + v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s))) + + if v == 0: + v = 1 + c = 1 + s = 0 + else: + c /= v + s /= v + + cc = ctx.conj(c) + cs = ctx.conj(s) + + for k in xrange(n0, n): + # apply givens rotation from the left + x = A[n0 ,k] + y = A[n0+1,k] + A[n0 ,k] = cc * x + cs * y + A[n0+1,k] = c * y - s * x + + for k in xrange(min(n1, n0+3)): + # apply givens rotation from the right + x = A[k,n0 ] + y = A[k,n0+1] + A[k,n0 ] = c * x + s * y + A[k,n0+1] = cc * y - cs * x + + if not isinstance(Q, bool): + for k in xrange(n): + # eigenvectors + x = Q[k,n0 ] + y = Q[k,n0+1] + Q[k,n0 ] = c * x + s * y + Q[k,n0+1] = cc * y - cs * x + + # chase the bulge + + for j in xrange(n0, n1 - 2): + # calculate givens rotation + + c = A[j+1,j] + s = A[j+2,j] + + v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s))) + + if v == 0: + A[j+1,j] = 0 + v = 1 + c = 1 + s = 0 + else: + A[j+1,j] = v + c /= v + s /= v + + A[j+2,j] = 0 + + cc = ctx.conj(c) + cs = ctx.conj(s) + + for k in xrange(j+1, n): + # apply givens rotation from the left + x = A[j+1,k] + y = A[j+2,k] + A[j+1,k] = cc * x + cs * y + A[j+2,k] = c * y - s * x + + for k in xrange(0, min(n1, j+4)): + # apply givens rotation from the right + x = A[k,j+1] + y = A[k,j+2] + A[k,j+1] = c * x + s * y + A[k,j+2] = cc * y - cs * x + + if not isinstance(Q, bool): + for k in xrange(0, n): + # eigenvectors + x = Q[k,j+1] + y = Q[k,j+2] + Q[k,j+1] = c * x + s * y + Q[k,j+2] = cc * y - cs * x + + + +def hessenberg_qr(ctx, A, Q): + """ + This routine computes the Schur decomposition of an upper Hessenberg matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = R and Q' Q = Q Q' = 1 + + where R is an upper right triangular matrix. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). + + parameters: + A (input/output) On input, A contains an upper Hessenberg matrix. + On output, A is replace by the upper right triangluar matrix R. + + Q (input/output) The parameter Q is multiplied by the unitary + matrix Q arising from the Schur decomposition. Q can also be + false, in which case the unitary matrix Q is not computated. + """ + + n = A.rows + + norm = 0 + for x in xrange(n): + for y in xrange(min(x+2, n)): + norm += ctx.re(A[y,x]) ** 2 + ctx.im(A[y,x]) ** 2 + norm = ctx.sqrt(norm) / n + + if norm == 0: + return + + n0 = 0 + n1 = n + + eps = ctx.eps / (100 * n) + maxits = ctx.dps * 4 + + its = totalits = 0 + + while 1: + # kressner p.32 algo 3 + # the active submatrix is A[n0:n1,n0:n1] + + k = n0 + + while k + 1 < n1: + s = abs(ctx.re(A[k,k])) + abs(ctx.im(A[k,k])) + abs(ctx.re(A[k+1,k+1])) + abs(ctx.im(A[k+1,k+1])) + if s < eps * norm: + s = norm + if abs(A[k+1,k]) < eps * s: + break + k += 1 + + if k + 1 < n1: + # deflation found at position (k+1, k) + + A[k+1,k] = 0 + n0 = k + 1 + + its = 0 + + if n0 + 1 >= n1: + # block of size at most two has converged + n0 = 0 + n1 = k + 1 + if n1 < 2: + # QR algorithm has converged + return + else: + if (its % 30) == 10: + # exceptional shift + shift = A[n1-1,n1-2] + elif (its % 30) == 20: + # exceptional shift + shift = abs(A[n1-1,n1-2]) + elif (its % 30) == 29: + # exceptional shift + shift = norm + else: + # A = [ a b ] det(x-A)=x*x-x*tr(A)+det(A) + # [ c d ] + # + # eigenvalues bad: (tr(A)+sqrt((tr(A))**2-4*det(A)))/2 + # bad because of cancellation if |c| is small and |a-d| is small, too. + # + # eigenvalues good: (a+d+sqrt((a-d)**2+4*b*c))/2 + + t = A[n1-2,n1-2] + A[n1-1,n1-1] + s = (A[n1-1,n1-1] - A[n1-2,n1-2]) ** 2 + 4 * A[n1-1,n1-2] * A[n1-2,n1-1] + if ctx.re(s) > 0: + s = ctx.sqrt(s) + else: + s = ctx.sqrt(-s) * 1j + a = (t + s) / 2 + b = (t - s) / 2 + if abs(A[n1-1,n1-1] - a) > abs(A[n1-1,n1-1] - b): + shift = b + else: + shift = a + + its += 1 + totalits += 1 + + qr_step(ctx, n0, n1, A, Q, shift) + + if its > maxits: + raise RuntimeError("qr: failed to converge after %d steps" % its) + + +@defun +def schur(ctx, A, overwrite_a = False): + """ + This routine computes the Schur decomposition of a square matrix A. + Given A, an unitary matrix Q is determined such that + + Q' A Q = R and Q' Q = Q Q' = 1 + + where R is an upper right triangular matrix. Here ' denotes the + hermitian transpose (i.e. transposition and conjugation). + + input: + A : a real or complex square matrix + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + Q : an unitary matrix + R : an upper right triangular matrix + + return value: (Q, R) + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> Q, R = mp.schur(A) + >>> mp.nprint(R, 3) # doctest:+SKIP + [2.0 0.417 -2.53] + [0.0 4.0 -4.74] + [0.0 0.0 9.0] + >>> print(mp.chop(A - Q * R * Q.transpose_conj())) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + warning: The Schur decomposition is not unique. + """ + + n = A.rows + + if n == 1: + return (ctx.matrix([[1]]), A) + + if not overwrite_a: + A = A.copy() + + T = ctx.matrix(n, 1) + + hessenberg_reduce_0(ctx, A, T) + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + + for x in xrange(n): + for y in xrange(x + 2, n): + A[y,x] = 0 + + hessenberg_qr(ctx, A, Q) + + return Q, A + + +def eig_tr_r(ctx, A): + """ + This routine calculates the right eigenvectors of an upper right triangular matrix. + + input: + A an upper right triangular matrix + + output: + ER a matrix whose columns form the right eigenvectors of A + + return value: ER + """ + + # this subroutine is inspired by the lapack routines ctrevc.f,clatrs.f + + n = A.rows + + ER = ctx.eye(n) + + eps = ctx.eps + + unfl = ctx.ldexp(ctx.one, -ctx.prec * 30) + # since mpmath effectively has no limits on the exponent, we simply scale doubles up + # original double has prec*20 + + smlnum = unfl * (n / eps) + simin = 1 / ctx.sqrt(eps) + + rmax = 1 + + for i in xrange(1, n): + s = A[i,i] + + smin = max(eps * abs(s), smlnum) + + for j in xrange(i - 1, -1, -1): + + r = 0 + for k in xrange(j + 1, i + 1): + r += A[j,k] * ER[k,i] + + t = A[j,j] - s + if abs(t) < smin: + t = smin + + r = -r / t + ER[j,i] = r + + rmax = max(rmax, abs(r)) + if rmax > simin: + for k in xrange(j, i+1): + ER[k,i] /= rmax + rmax = 1 + + if rmax != 1: + for k in xrange(0, i + 1): + ER[k,i] /= rmax + + return ER + +def eig_tr_l(ctx, A): + """ + This routine calculates the left eigenvectors of an upper right triangular matrix. + + input: + A an upper right triangular matrix + + output: + EL a matrix whose rows form the left eigenvectors of A + + return value: EL + """ + + n = A.rows + + EL = ctx.eye(n) + + eps = ctx.eps + + unfl = ctx.ldexp(ctx.one, -ctx.prec * 30) + # since mpmath effectively has no limits on the exponent, we simply scale doubles up + # original double has prec*20 + + smlnum = unfl * (n / eps) + simin = 1 / ctx.sqrt(eps) + + rmax = 1 + + for i in xrange(0, n - 1): + s = A[i,i] + + smin = max(eps * abs(s), smlnum) + + for j in xrange(i + 1, n): + + r = 0 + for k in xrange(i, j): + r += EL[i,k] * A[k,j] + + t = A[j,j] - s + if abs(t) < smin: + t = smin + + r = -r / t + EL[i,j] = r + + rmax = max(rmax, abs(r)) + if rmax > simin: + for k in xrange(i, j + 1): + EL[i,k] /= rmax + rmax = 1 + + if rmax != 1: + for k in xrange(i, n): + EL[i,k] /= rmax + + return EL + +@defun +def eig(ctx, A, left = False, right = True, overwrite_a = False): + """ + This routine computes the eigenvalues and optionally the left and right + eigenvectors of a square matrix A. Given A, a vector E and matrices ER + and EL are calculated such that + + A ER[:,i] = E[i] ER[:,i] + EL[i,:] A = EL[i,:] E[i] + + E contains the eigenvalues of A. The columns of ER contain the right eigenvectors + of A whereas the rows of EL contain the left eigenvectors. + + + input: + A : a real or complex square matrix of shape (n, n) + left : if true, the left eigenvectors are calculated. + right : if true, the right eigenvectors are calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + E : a list of length n containing the eigenvalues of A. + ER : a matrix whose columns contain the right eigenvectors of A. + EL : a matrix whose rows contain the left eigenvectors of A. + + return values: + E if left and right are both false. + (E, ER) if right is true and left is false. + (E, EL) if left is true and right is false. + (E, EL, ER) if left and right are true. + + + examples: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> E, ER = mp.eig(A) + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + + >>> E, EL, ER = mp.eig(A,left = True, right = True) + >>> E, EL, ER = mp.eig_sort(E, EL, ER) + >>> mp.nprint(E) + [2.0, 4.0, 9.0] + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0])) + [0.0 0.0 0.0] + + warning: + - If there are multiple eigenvalues, the eigenvectors do not necessarily + span the whole vectorspace, i.e. ER and EL may have not full rank. + Furthermore in that case the eigenvectors are numerical ill-conditioned. + - In the general case the eigenvalues have no natural order. + + see also: + - eigh (or eigsy, eighe) for the symmetric eigenvalue problem. + - eig_sort for sorting of eigenvalues and eigenvectors + """ + + n = A.rows + + if n == 1: + if left and (not right): + return ([A[0]], ctx.matrix([[1]])) + + if right and (not left): + return ([A[0]], ctx.matrix([[1]])) + + return ([A[0]], ctx.matrix([[1]]), ctx.matrix([[1]])) + + if not overwrite_a: + A = A.copy() + + T = ctx.zeros(n, 1) + + hessenberg_reduce_0(ctx, A, T) + + if left or right: + Q = A.copy() + hessenberg_reduce_1(ctx, Q, T) + else: + Q = False + + for x in xrange(n): + for y in xrange(x + 2, n): + A[y,x] = 0 + + hessenberg_qr(ctx, A, Q) + + E = [0 for i in xrange(n)] + for i in xrange(n): + E[i] = A[i,i] + + if not (left or right): + return E + + if left: + EL = eig_tr_l(ctx, A) + EL = EL * Q.transpose_conj() + + if right: + ER = eig_tr_r(ctx, A) + ER = Q * ER + + if left and (not right): + return (E, EL) + + if right and (not left): + return (E, ER) + + return (E, EL, ER) + +@defun +def eig_sort(ctx, E, EL = False, ER = False, f = "real"): + """ + This routine sorts the eigenvalues and eigenvectors delivered by ``eig``. + + parameters: + E : the eigenvalues as delivered by eig + EL : the left eigenvectors as delivered by eig, or false + ER : the right eigenvectors as delivered by eig, or false + f : either a string ("real" sort by increasing real part, "imag" sort by + increasing imag part, "abs" sort by absolute value) or a function + mapping complexs to the reals, i.e. ``f = lambda x: -mp.re(x) `` + would sort the eigenvalues by decreasing real part. + + return values: + E if EL and ER are both false. + (E, ER) if ER is not false and left is false. + (E, EL) if EL is not false and right is false. + (E, EL, ER) if EL and ER are not false. + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]]) + >>> E, EL, ER = mp.eig(A,left = True, right = True) + >>> E, EL, ER = mp.eig_sort(E, EL, ER) + >>> mp.nprint(E) + [2.0, 4.0, 9.0] + >>> E, EL, ER = mp.eig_sort(E, EL, ER,f = lambda x: -mp.re(x)) + >>> mp.nprint(E) + [9.0, 4.0, 2.0] + >>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0])) + [0.0] + [0.0] + [0.0] + >>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0])) + [0.0 0.0 0.0] + """ + + if isinstance(f, str): + if f == "real": + f = ctx.re + elif f == "imag": + f = ctx.im + elif f == "abs": + f = abs + else: + raise RuntimeError("unknown function %s" % f) + + n = len(E) + + # Sort eigenvalues (bubble-sort) + + for i in xrange(n): + imax = i + s = f(E[i]) # s is the current maximal element + + for j in xrange(i + 1, n): + c = f(E[j]) + if c < s: + s = c + imax = j + + if imax != i: + # swap eigenvalues + + z = E[i] + E[i] = E[imax] + E[imax] = z + + if not isinstance(EL, bool): + for j in xrange(n): + z = EL[i,j] + EL[i,j] = EL[imax,j] + EL[imax,j] = z + + if not isinstance(ER, bool): + for j in xrange(n): + z = ER[j,i] + ER[j,i] = ER[j,imax] + ER[j,imax] = z + + if isinstance(EL, bool) and isinstance(ER, bool): + return E + + if isinstance(EL, bool) and not(isinstance(ER, bool)): + return (E, ER) + + if isinstance(ER, bool) and not(isinstance(EL, bool)): + return (E, EL) + + return (E, EL, ER) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen_symmetric.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen_symmetric.py new file mode 100644 index 0000000000000000000000000000000000000000..c82c0bb061d22c37a89f82a0b9bdab3e9ba7ddde --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/eigen_symmetric.py @@ -0,0 +1,1807 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +################################################################################################## +# module for the symmetric eigenvalue problem +# Copyright 2013 Timo Hartmann (thartmann15 at gmail.com) +# +# todo: +# - implement balancing +# +################################################################################################## + +""" +The symmetric eigenvalue problem. +--------------------------------- + +This file contains routines for the symmetric eigenvalue problem. + +high level routines: + + eigsy : real symmetric (ordinary) eigenvalue problem + eighe : complex hermitian (ordinary) eigenvalue problem + eigh : unified interface for eigsy and eighe + svd_r : singular value decomposition for real matrices + svd_c : singular value decomposition for complex matrices + svd : unified interface for svd_r and svd_c + + +low level routines: + + r_sy_tridiag : reduction of real symmetric matrix to real symmetric tridiagonal matrix + c_he_tridiag_0 : reduction of complex hermitian matrix to real symmetric tridiagonal matrix + c_he_tridiag_1 : auxiliary routine to c_he_tridiag_0 + c_he_tridiag_2 : auxiliary routine to c_he_tridiag_0 + tridiag_eigen : solves the real symmetric tridiagonal matrix eigenvalue problem + svd_r_raw : raw singular value decomposition for real matrices + svd_c_raw : raw singular value decomposition for complex matrices +""" + +from ..libmp.backend import xrange +from .eigen import defun + + +def r_sy_tridiag(ctx, A, D, E, calc_ev = True): + """ + This routine transforms a real symmetric matrix A to a real symmetric + tridiagonal matrix T using an orthogonal similarity transformation: + Q' * A * Q = T (here ' denotes the matrix transpose). + The orthogonal matrix Q is build up from Householder reflectors. + + parameters: + A (input/output) On input, A contains the real symmetric matrix of + dimension (n,n). On output, if calc_ev is true, A contains the + orthogonal matrix Q, otherwise A is destroyed. + + D (output) real array of length n, contains the diagonal elements + of the tridiagonal matrix + + E (output) real array of length n, contains the offdiagonal elements + of the tridiagonal matrix in E[0:(n-1)] where is the dimension of + the matrix A. E[n-1] is undefined. + + calc_ev (input) If calc_ev is true, this routine explicitly calculates the + orthogonal matrix Q which is then returned in A. If calc_ev is + false, Q is not explicitly calculated resulting in a shorter run time. + + This routine is a python translation of the fortran routine tred2.f in the + software library EISPACK (see netlib.org) which itself is based on the algol + procedure tred2 described in: + - Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson + - Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) + + For a good introduction to Householder reflections, see also + Stoer, Bulirsch - Introduction to Numerical Analysis. + """ + + # note : the vector v of the i-th houshoulder reflector is stored in a[(i+1):,i] + # whereas v/ is stored in a[i,(i+1):] + + n = A.rows + for i in xrange(n - 1, 0, -1): + # scale the vector + + scale = 0 + for k in xrange(0, i): + scale += abs(A[k,i]) + + scale_inv = 0 + if scale != 0: + scale_inv = 1/scale + + # sadly there are floating point numbers not equal to zero whose reciprocal is infinity + + if i == 1 or scale == 0 or ctx.isinf(scale_inv): + E[i] = A[i-1,i] # nothing to do + D[i] = 0 + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in xrange(0, i): + A[k,i] *= scale_inv + H += A[k,i] * A[k,i] + + F = A[i-1,i] + G = ctx.sqrt(H) + if F > 0: + G = -G + E[i] = scale * G + H -= F * G + A[i-1,i] = F - G + F = 0 + + # apply housholder transformation + + for j in xrange(0, i): + if calc_ev: + A[i,j] = A[j,i] / H + + G = 0 # calculate A*U + for k in xrange(0, j + 1): + G += A[k,j] * A[k,i] + for k in xrange(j + 1, i): + G += A[j,k] * A[k,i] + + E[j] = G / H # calculate P + F += E[j] * A[j,i] + + HH = F / (2 * H) + + for j in xrange(0, i): # calculate reduced A + F = A[j,i] + G = E[j] - HH * F # calculate Q + E[j] = G + + for k in xrange(0, j + 1): + A[k,j] -= F * E[k] + G * A[k,i] + + D[i] = H + + for i in xrange(1, n): # better for compatibility + E[i-1] = E[i] + E[n-1] = 0 + + if calc_ev: + D[0] = 0 + for i in xrange(0, n): + if D[i] != 0: + for j in xrange(0, i): # accumulate transformation matrices + G = 0 + for k in xrange(0, i): + G += A[i,k] * A[k,j] + for k in xrange(0, i): + A[k,j] -= G * A[k,i] + + D[i] = A[i,i] + A[i,i] = 1 + + for j in xrange(0, i): + A[j,i] = A[i,j] = 0 + else: + for i in xrange(0, n): + D[i] = A[i,i] + + + + + +def c_he_tridiag_0(ctx, A, D, E, T): + """ + This routine transforms a complex hermitian matrix A to a real symmetric + tridiagonal matrix T using an unitary similarity transformation: + Q' * A * Q = T (here ' denotes the hermitian matrix transpose, + i.e. transposition und conjugation). + The unitary matrix Q is build up from Householder reflectors and + an unitary diagonal matrix. + + parameters: + A (input/output) On input, A contains the complex hermitian matrix + of dimension (n,n). On output, A contains the unitary matrix Q + in compressed form. + + D (output) real array of length n, contains the diagonal elements + of the tridiagonal matrix. + + E (output) real array of length n, contains the offdiagonal elements + of the tridiagonal matrix in E[0:(n-1)] where is the dimension of + the matrix A. E[n-1] is undefined. + + T (output) complex array of length n, contains a unitary diagonal + matrix. + + This routine is a python translation (in slightly modified form) of the fortran + routine htridi.f in the software library EISPACK (see netlib.org) which itself + is a complex version of the algol procedure tred1 described in: + - Num. Math. 11, p.181-195 (1968) by Martin, Reinsch and Wilkonson + - Handbook for auto. comp., Vol II, Linear Algebra, p.212-226 (1971) + + For a good introduction to Householder reflections, see also + Stoer, Bulirsch - Introduction to Numerical Analysis. + """ + + n = A.rows + T[n-1] = 1 + for i in xrange(n - 1, 0, -1): + + # scale the vector + + scale = 0 + for k in xrange(0, i): + scale += abs(ctx.re(A[k,i])) + abs(ctx.im(A[k,i])) + + scale_inv = 0 + if scale != 0: + scale_inv = 1 / scale + + # sadly there are floating point numbers not equal to zero whose reciprocal is infinity + + if scale == 0 or ctx.isinf(scale_inv): + E[i] = 0 + D[i] = 0 + T[i-1] = 1 + continue + + if i == 1: + F = A[i-1,i] + f = abs(F) + E[i] = f + D[i] = 0 + if f != 0: + T[i-1] = T[i] * F / f + else: + T[i-1] = T[i] + continue + + # calculate parameters for housholder transformation + + H = 0 + for k in xrange(0, i): + A[k,i] *= scale_inv + rr = ctx.re(A[k,i]) + ii = ctx.im(A[k,i]) + H += rr * rr + ii * ii + + F = A[i-1,i] + f = abs(F) + G = ctx.sqrt(H) + H += G * f + E[i] = scale * G + if f != 0: + F = F / f + TZ = - T[i] * F # T[i-1]=-T[i]*F, but we need T[i-1] as temporary storage + G *= F + else: + TZ = -T[i] # T[i-1]=-T[i] + A[i-1,i] += G + F = 0 + + # apply housholder transformation + + for j in xrange(0, i): + A[i,j] = A[j,i] / H + + G = 0 # calculate A*U + for k in xrange(0, j + 1): + G += ctx.conj(A[k,j]) * A[k,i] + for k in xrange(j + 1, i): + G += A[j,k] * A[k,i] + + T[j] = G / H # calculate P + F += ctx.conj(T[j]) * A[j,i] + + HH = F / (2 * H) + + for j in xrange(0, i): # calculate reduced A + F = A[j,i] + G = T[j] - HH * F # calculate Q + T[j] = G + + for k in xrange(0, j + 1): + A[k,j] -= ctx.conj(F) * T[k] + ctx.conj(G) * A[k,i] + # as we use the lower left part for storage + # we have to use the transpose of the normal formula + + T[i-1] = TZ + D[i] = H + + for i in xrange(1, n): # better for compatibility + E[i-1] = E[i] + E[n-1] = 0 + + D[0] = 0 + for i in xrange(0, n): + zw = D[i] + D[i] = ctx.re(A[i,i]) + A[i,i] = zw + + + + + + + +def c_he_tridiag_1(ctx, A, T): + """ + This routine forms the unitary matrix Q described in c_he_tridiag_0. + + parameters: + A (input/output) On input, A is the same matrix as delivered by + c_he_tridiag_0. On output, A is set to Q. + + T (input) On input, T is the same array as delivered by c_he_tridiag_0. + + """ + + n = A.rows + + for i in xrange(0, n): + if A[i,i] != 0: + for j in xrange(0, i): + G = 0 + for k in xrange(0, i): + G += ctx.conj(A[i,k]) * A[k,j] + for k in xrange(0, i): + A[k,j] -= G * A[k,i] + + A[i,i] = 1 + + for j in xrange(0, i): + A[j,i] = A[i,j] = 0 + + for i in xrange(0, n): + for k in xrange(0, n): + A[i,k] *= T[k] + + + + +def c_he_tridiag_2(ctx, A, T, B): + """ + This routine applied the unitary matrix Q described in c_he_tridiag_0 + onto the the matrix B, i.e. it forms Q*B. + + parameters: + A (input) On input, A is the same matrix as delivered by c_he_tridiag_0. + + T (input) On input, T is the same array as delivered by c_he_tridiag_0. + + B (input/output) On input, B is a complex matrix. On output B is replaced + by Q*B. + + This routine is a python translation of the fortran routine htribk.f in the + software library EISPACK (see netlib.org). See c_he_tridiag_0 for more + references. + """ + + n = A.rows + + for i in xrange(0, n): + for k in xrange(0, n): + B[k,i] *= T[k] + + for i in xrange(0, n): + if A[i,i] != 0: + for j in xrange(0, n): + G = 0 + for k in xrange(0, i): + G += ctx.conj(A[i,k]) * B[k,j] + for k in xrange(0, i): + B[k,j] -= G * A[k,i] + + + + + +def tridiag_eigen(ctx, d, e, z = False): + """ + This subroutine find the eigenvalues and the first components of the + eigenvectors of a real symmetric tridiagonal matrix using the implicit + QL method. + + parameters: + + d (input/output) real array of length n. on input, d contains the diagonal + elements of the input matrix. on output, d contains the eigenvalues in + ascending order. + + e (input) real array of length n. on input, e contains the offdiagonal + elements of the input matrix in e[0:(n-1)]. On output, e has been + destroyed. + + z (input/output) If z is equal to False, no eigenvectors will be computed. + Otherwise on input z should have the format z[0:m,0:n] (i.e. a real or + complex matrix of dimension (m,n) ). On output this matrix will be + multiplied by the matrix of the eigenvectors (i.e. the columns of this + matrix are the eigenvectors): z --> z*EV + That means if z[i,j]={1 if j==j; 0 otherwise} on input, then on output + z will contain the first m components of the eigenvectors. That means + if m is equal to n, the i-th eigenvector will be z[:,i]. + + This routine is a python translation (in slightly modified form) of the + fortran routine imtql2.f in the software library EISPACK (see netlib.org) + which itself is based on the algol procudure imtql2 desribed in: + - num. math. 12, p. 377-383(1968) by matrin and wilkinson + - modified in num. math. 15, p. 450(1970) by dubrulle + - handbook for auto. comp., vol. II-linear algebra, p. 241-248 (1971) + See also the routine gaussq.f in netlog.org or acm algorithm 726. + """ + + n = len(d) + e[n-1] = 0 + iterlim = 2 * ctx.dps + + for l in xrange(n): + j = 0 + while 1: + m = l + while 1: + # look for a small subdiagonal element + if m + 1 == n: + break + if abs(e[m]) <= ctx.eps * (abs(d[m]) + abs(d[m + 1])): + break + m = m + 1 + if m == l: + break + + if j >= iterlim: + raise RuntimeError("tridiag_eigen: no convergence to an eigenvalue after %d iterations" % iterlim) + + j += 1 + + # form shift + + p = d[l] + g = (d[l + 1] - p) / (2 * e[l]) + r = ctx.hypot(g, 1) + + if g < 0: + s = g - r + else: + s = g + r + + g = d[m] - p + e[l] / s + + s, c, p = 1, 1, 0 + + for i in xrange(m - 1, l - 1, -1): + f = s * e[i] + b = c * e[i] + if abs(f) > abs(g): # this here is a slight improvement also used in gaussq.f or acm algorithm 726. + c = g / f + r = ctx.hypot(c, 1) + e[i + 1] = f * r + s = 1 / r + c = c * s + else: + s = f / g + r = ctx.hypot(s, 1) + e[i + 1] = g * r + c = 1 / r + s = s * c + g = d[i + 1] - p + r = (d[i] - g) * s + 2 * c * b + p = s * r + d[i + 1] = g + p + g = c * r - b + + if not isinstance(z, bool): + # calculate eigenvectors + for w in xrange(z.rows): + f = z[w,i+1] + z[w,i+1] = s * z[w,i] + c * f + z[w,i ] = c * z[w,i] - s * f + + d[l] = d[l] - p + e[l] = g + e[m] = 0 + + for ii in xrange(1, n): + # sort eigenvalues and eigenvectors (bubble-sort) + i = ii - 1 + k = i + p = d[i] + for j in xrange(ii, n): + if d[j] >= p: + continue + k = j + p = d[k] + if k == i: + continue + d[k] = d[i] + d[i] = p + + if not isinstance(z, bool): + for w in xrange(z.rows): + p = z[w,i] + z[w,i] = z[w,k] + z[w,k] = p + +######################################################################################## + +@defun +def eigsy(ctx, A, eigvals_only = False, overwrite_a = False): + """ + This routine solves the (ordinary) eigenvalue problem for a real symmetric + square matrix A. Given A, an orthogonal matrix Q is calculated which + diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) is a diagonal matrix whose diagonal is E. + ' denotes the transpose. + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + + input: + + A: real matrix of format (n,n) which is symmetric + (i.e. A=A' or A[i,j]=A[j,i]) + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: orthogonal matrix of format (n,n). contains the eigenvectors + of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, 2], [2, 0]]) + >>> E = mp.eigsy(A, eigvals_only = True) + >>> print(E) + [-1.0] + [ 4.0] + + >>> A = mp.matrix([[1, 2], [2, 3]]) + >>> E, Q = mp.eigsy(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eighe, eigh, eig + """ + + if not overwrite_a: + A = A.copy() + + d = ctx.zeros(A.rows, 1) + e = ctx.zeros(A.rows, 1) + + if eigvals_only: + r_sy_tridiag(ctx, A, d, e, calc_ev = False) + tridiag_eigen(ctx, d, e, False) + return d + else: + r_sy_tridiag(ctx, A, d, e, calc_ev = True) + tridiag_eigen(ctx, d, e, A) + return (d, A) + + +@defun +def eighe(ctx, A, eigvals_only = False, overwrite_a = False): + """ + This routine solves the (ordinary) eigenvalue problem for a complex + hermitian square matrix A. Given A, an unitary matrix Q is calculated which + diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) a is diagonal matrix whose diagonal is E. + ' denotes the hermitian transpose (i.e. ordinary transposition and + complex conjugation). + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + + input: + + A: complex matrix of format (n,n) which is hermitian + (i.e. A=A' or A[i,j]=conj(A[j,i])) + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: unitary matrix of format (n,n). contains the eigenvectors + of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[1, -3 - 1j], [-3 + 1j, -2]]) + >>> E = mp.eighe(A, eigvals_only = True) + >>> print(E) + [-4.0] + [ 3.0] + + >>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) + >>> E, Q = mp.eighe(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eigsy, eigh, eig + """ + + if not overwrite_a: + A = A.copy() + + d = ctx.zeros(A.rows, 1) + e = ctx.zeros(A.rows, 1) + t = ctx.zeros(A.rows, 1) + + if eigvals_only: + c_he_tridiag_0(ctx, A, d, e, t) + tridiag_eigen(ctx, d, e, False) + return d + else: + c_he_tridiag_0(ctx, A, d, e, t) + B = ctx.eye(A.rows) + tridiag_eigen(ctx, d, e, B) + c_he_tridiag_2(ctx, A, t, B) + return (d, B) + +@defun +def eigh(ctx, A, eigvals_only = False, overwrite_a = False): + """ + "eigh" is a unified interface for "eigsy" and "eighe". Depending on + whether A is real or complex the appropriate function is called. + + This routine solves the (ordinary) eigenvalue problem for a real symmetric + or complex hermitian square matrix A. Given A, an orthogonal (A real) or + unitary (A complex) matrix Q is calculated which diagonalizes A: + + Q' A Q = diag(E) and Q Q' = Q' Q = 1 + + Here diag(E) a is diagonal matrix whose diagonal is E. + ' denotes the hermitian transpose (i.e. ordinary transposition and + complex conjugation). + + The columns of Q are the eigenvectors of A and E contains the eigenvalues: + + A Q[:,i] = E[i] Q[:,i] + + input: + + A: a real or complex square matrix of format (n,n) which is symmetric + (i.e. A[i,j]=A[j,i]) or hermitian (i.e. A[i,j]=conj(A[j,i])). + + eigvals_only: if true, calculates only the eigenvalues E. + if false, calculates both eigenvectors and eigenvalues. + + overwrite_a: if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + + E: vector of format (n). contains the eigenvalues of A in ascending order. + + Q: an orthogonal or unitary matrix of format (n,n). contains the + eigenvectors of A as columns. + + return value: + + E if eigvals_only is true + (E, Q) if eigvals_only is false + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[3, 2], [2, 0]]) + >>> E = mp.eigh(A, eigvals_only = True) + >>> print(E) + [-1.0] + [ 4.0] + + >>> A = mp.matrix([[1, 2], [2, 3]]) + >>> E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + >>> A = mp.matrix([[1, 2 + 5j], [2 - 5j, 3]]) + >>> E, Q = mp.eigh(A) + >>> print(mp.chop(A * Q[:,0] - E[0] * Q[:,0])) + [0.0] + [0.0] + + see also: eigsy, eighe, eig + """ + + iscomplex = any(type(x) is ctx.mpc for x in A) + + if iscomplex: + return ctx.eighe(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) + else: + return ctx.eigsy(A, eigvals_only = eigvals_only, overwrite_a = overwrite_a) + + +@defun +def gauss_quadrature(ctx, n, qtype = "legendre", alpha = 0, beta = 0): + """ + This routine calulates gaussian quadrature rules for different + families of orthogonal polynomials. Let (a, b) be an interval, + W(x) a positive weight function and n a positive integer. + Then the purpose of this routine is to calculate pairs (x_k, w_k) + for k=0, 1, 2, ... (n-1) which give + + int(W(x) * F(x), x = a..b) = sum(w_k * F(x_k),k = 0..(n-1)) + + exact for all polynomials F(x) of degree (strictly) less than 2*n. For all + integrable functions F(x) the sum is a (more or less) good approximation to + the integral. The x_k are called nodes (which are the zeros of the + related orthogonal polynomials) and the w_k are called the weights. + + parameters + n (input) The degree of the quadrature rule, i.e. its number of + nodes. + + qtype (input) The family of orthogonal polynmomials for which to + compute the quadrature rule. See the list below. + + alpha (input) real number, used as parameter for some orthogonal + polynomials + + beta (input) real number, used as parameter for some orthogonal + polynomials. + + return value + + (X, W) a pair of two real arrays where x_k = X[k] and w_k = W[k]. + + + orthogonal polynomials: + + qtype polynomial + ----- ---------- + + "legendre" Legendre polynomials, W(x)=1 on the interval (-1, +1) + "legendre01" shifted Legendre polynomials, W(x)=1 on the interval (0, +1) + "hermite" Hermite polynomials, W(x)=exp(-x*x) on (-infinity,+infinity) + "laguerre" Laguerre polynomials, W(x)=exp(-x) on (0,+infinity) + "glaguerre" generalized Laguerre polynomials, W(x)=exp(-x)*x**alpha + on (0, +infinity) + "chebyshev1" Chebyshev polynomials of the first kind, W(x)=1/sqrt(1-x*x) + on (-1, +1) + "chebyshev2" Chebyshev polynomials of the second kind, W(x)=sqrt(1-x*x) + on (-1, +1) + "jacobi" Jacobi polynomials, W(x)=(1-x)**alpha * (1+x)**beta on (-1, +1) + with alpha>-1 and beta>-1 + + examples: + >>> from mpmath import mp + >>> f = lambda x: x**8 + 2 * x**6 - 3 * x**4 + 5 * x**2 - 7 + >>> X, W = mp.gauss_quadrature(5, "hermite") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> B = mp.sqrt(mp.pi) * 57 / 16 + >>> C = mp.quad(lambda x: mp.exp(- x * x) * f(x), [-mp.inf, +mp.inf]) + >>> mp.nprint((mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))) + (0.0, 0.0) + + >>> f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 + >>> X, W = mp.gauss_quadrature(3, "laguerre") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> B = 76 + >>> C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) + >>> mp.nprint(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10)) + .0 + + # orthogonality of the chebyshev polynomials: + >>> f = lambda x: mp.chebyt(3, x) * mp.chebyt(2, x) + >>> X, W = mp.gauss_quadrature(3, "chebyshev1") + >>> A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) + >>> print(mp.chop(A, tol = 1e-10)) + 0.0 + + references: + - golub and welsch, "calculations of gaussian quadrature rules", mathematics of + computation 23, p. 221-230 (1969) + - golub, "some modified matrix eigenvalue problems", siam review 15, p. 318-334 (1973) + - stroud and secrest, "gaussian quadrature formulas", prentice-hall (1966) + + See also the routine gaussq.f in netlog.org or ACM Transactions on + Mathematical Software algorithm 726. + """ + + d = ctx.zeros(n, 1) + e = ctx.zeros(n, 1) + z = ctx.zeros(1, n) + + z[0,0] = 1 + + if qtype == "legendre": + # legendre on the range -1 +1 , abramowitz, table 25.4, p.916 + w = 2 + for i in xrange(n): + j = i + 1 + e[i] = ctx.sqrt(j * j / (4 * j * j - ctx.mpf(1))) + elif qtype == "legendre01": + # legendre shifted to 0 1 , abramowitz, table 25.8, p.921 + w = 1 + for i in xrange(n): + d[i] = 1 / ctx.mpf(2) + j = i + 1 + e[i] = ctx.sqrt(j * j / (16 * j * j - ctx.mpf(4))) + elif qtype == "hermite": + # hermite on the range -inf +inf , abramowitz, table 25.10,p.924 + w = ctx.sqrt(ctx.pi) + for i in xrange(n): + j = i + 1 + e[i] = ctx.sqrt(j / ctx.mpf(2)) + elif qtype == "laguerre": + # laguerre on the range 0 +inf , abramowitz, table 25.9, p. 923 + w = 1 + for i in xrange(n): + j = i + 1 + d[i] = 2 * j - 1 + e[i] = j + elif qtype=="chebyshev1": + # chebyshev polynimials of the first kind + w = ctx.pi + for i in xrange(n): + e[i] = 1 / ctx.mpf(2) + e[0] = ctx.sqrt(1 / ctx.mpf(2)) + elif qtype == "chebyshev2": + # chebyshev polynimials of the second kind + w = ctx.pi / 2 + for i in xrange(n): + e[i] = 1 / ctx.mpf(2) + elif qtype == "glaguerre": + # generalized laguerre on the range 0 +inf + w = ctx.gamma(1 + alpha) + for i in xrange(n): + j = i + 1 + d[i] = 2 * j - 1 + alpha + e[i] = ctx.sqrt(j * (j + alpha)) + elif qtype == "jacobi": + # jacobi polynomials + alpha = ctx.mpf(alpha) + beta = ctx.mpf(beta) + ab = alpha + beta + abi = ab + 2 + w = (2**(ab+1)) * ctx.gamma(alpha + 1) * ctx.gamma(beta + 1) / ctx.gamma(abi) + d[0] = (beta - alpha) / abi + e[0] = ctx.sqrt(4 * (1 + alpha) * (1 + beta) / ((abi + 1) * (abi * abi))) + a2b2 = beta * beta - alpha * alpha + for i in xrange(1, n): + j = i + 1 + abi = 2 * j + ab + d[i] = a2b2 / ((abi - 2) * abi) + e[i] = ctx.sqrt(4 * j * (j + alpha) * (j + beta) * (j + ab) / ((abi * abi - 1) * abi * abi)) + elif isinstance(qtype, str): + raise ValueError("unknown quadrature rule \"%s\"" % qtype) + elif not isinstance(qtype, str): + w = qtype(d, e) + else: + assert 0 + + tridiag_eigen(ctx, d, e, z) + + for i in xrange(len(z)): + z[i] *= z[i] + + z = z.transpose() + return (d, w * z) + +################################################################################################## +################################################################################################## +################################################################################################## + +def svd_r_raw(ctx, A, V = False, calc_u = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal matrices U and V are calculated such that + + A = U S V + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + The diagonal elements of S are the singular values of A, i.e. the + squareroots of the eigenvalues of A' A or A A'. Here ' denotes the transpose. + Householder bidiagonalization and a variant of the QR algorithm is used. + + overview of the matrices : + + A : m*n A gets replaced by U + U : m*n U replaces A. If n>m then only the first m*m block of U is + non-zero. column-orthogonal: U' U = B + here B is a n*n matrix whose first min(m,n) diagonal + elements are 1 and all other elements are zero. + S : n*n diagonal matrix, only the diagonal elements are stored in + the array S. only the first min(m,n) diagonal elements are non-zero. + V : n*n orthogonal: V V' = V' V = 1 + + parameters: + A (input/output) On input, A contains a real matrix of shape m*n. + On output, if calc_u is true A contains the column-orthogonal + matrix U; otherwise A is simply used as workspace and thus destroyed. + + V (input/output) if false, the matrix V is not calculated. otherwise + V must be a matrix of shape n*n. + + calc_u (input) If true, the matrix U is calculated and replaces A. + if false, U is not calculated and A is simply destroyed + + return value: + S an array of length n containing the singular values of A sorted by + decreasing magnitude. only the first min(m,n) elements are non-zero. + + This routine is a python translation of the fortran routine svd.f in the + software library EISPACK (see netlib.org) which itself is based on the + algol procedure svd described in: + - num. math. 14, 403-420(1970) by golub and reinsch. + - wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). + + """ + + m, n = A.rows, A.cols + + S = ctx.zeros(n, 1) + + # work is a temporary array of size n + work = ctx.zeros(n, 1) + + g = scale = anorm = 0 + maxits = 3 * ctx.dps + + for i in xrange(n): # householder reduction to bidiagonal form + work[i] = scale*g + g = s = scale = 0 + if i < m: + for k in xrange(i, m): + scale += ctx.fabs(A[k,i]) + if scale != 0: + for k in xrange(i, m): + A[k,i] /= scale + s += A[k,i] * A[k,i] + f = A[i,i] + g = -ctx.sqrt(s) + if f < 0: + g = -g + h = f * g - s + A[i,i] = f - g + for j in xrange(i+1, n): + s = 0 + for k in xrange(i, m): + s += A[k,i] * A[k,j] + f = s / h + for k in xrange(i, m): + A[k,j] += f * A[k,i] + for k in xrange(i,m): + A[k,i] *= scale + + S[i] = scale * g + g = s = scale = 0 + + if i < m and i != n - 1: + for k in xrange(i+1, n): + scale += ctx.fabs(A[i,k]) + if scale: + for k in xrange(i+1, n): + A[i,k] /= scale + s += A[i,k] * A[i,k] + f = A[i,i+1] + g = -ctx.sqrt(s) + if f < 0: + g = -g + h = f * g - s + A[i,i+1] = f - g + + for k in xrange(i+1, n): + work[k] = A[i,k] / h + + for j in xrange(i+1, m): + s = 0 + for k in xrange(i+1, n): + s += A[j,k] * A[i,k] + for k in xrange(i+1, n): + A[j,k] += s * work[k] + + for k in xrange(i+1, n): + A[i,k] *= scale + + anorm = max(anorm, ctx.fabs(S[i]) + ctx.fabs(work[i])) + + if not isinstance(V, bool): + for i in xrange(n-2, -1, -1): # accumulation of right hand transformations + V[i+1,i+1] = 1 + + if work[i+1] != 0: + for j in xrange(i+1, n): + V[i,j] = (A[i,j] / A[i,i+1]) / work[i+1] + for j in xrange(i+1, n): + s = 0 + for k in xrange(i+1, n): + s += A[i,k] * V[j,k] + for k in xrange(i+1, n): + V[j,k] += s * V[i,k] + + for j in xrange(i+1, n): + V[j,i] = V[i,j] = 0 + + V[0,0] = 1 + + if m= maxits: + raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) + + x = S[l] # shift from bottom 2 by 2 minor + nm = k-1 + y = S[nm] + g = work[nm] + h = work[k] + f = ((y - z) * (y + z) + (g - h) * (g + h))/(2 * h * y) + g = ctx.hypot(f, 1) + if f >= 0: f = ((x - z) * (x + z) + h * ((y / (f + g)) - h)) / x + else: f = ((x - z) * (x + z) + h * ((y / (f - g)) - h)) / x + + c = s = 1 # next qt transformation + + for j in xrange(l, nm + 1): + g = work[j+1] + y = S[j+1] + h = s * g + g = c * g + z = ctx.hypot(f, h) + work[j] = z + c = f / z + s = h / z + f = x * c + g * s + g = g * c - x * s + h = y * s + y *= c + if not isinstance(V, bool): + for jj in xrange(n): + x = V[j ,jj] + z = V[j+1,jj] + V[j ,jj]= x * c + z * s + V[j+1 ,jj]= z * c - x * s + z = ctx.hypot(f, h) + S[j] = z + if z != 0: # rotation can be arbitray if z=0 + z = 1 / z + c = f * z + s = h * z + f = c * g + s * y + x = c * y - s * g + + if calc_u: + for jj in xrange(m): + y = A[jj,j ] + z = A[jj,j+1] + A[jj,j ] = y * c + z * s + A[jj,j+1 ] = z * c - y * s + + work[l] = 0 + work[k] = f + S[k] = x + + ########################## + + # Sort singular values into decreasing order (bubble-sort) + + for i in xrange(n): + imax = i + s = ctx.fabs(S[i]) # s is the current maximal element + + for j in xrange(i + 1, n): + c = ctx.fabs(S[j]) + if c > s: + s = c + imax = j + + if imax != i: + # swap singular values + + z = S[i] + S[i] = S[imax] + S[imax] = z + + if calc_u: + for j in xrange(m): + z = A[j,i] + A[j,i] = A[j,imax] + A[j,imax] = z + + if not isinstance(V, bool): + for j in xrange(n): + z = V[i,j] + V[i,j] = V[imax,j] + V[imax,j] = z + + return S + +####################### + +def svd_c_raw(ctx, A, V = False, calc_u = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two unitary matrices U and V are calculated such that + + A = U S V + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + The diagonal elements of S are the singular values of A, i.e. the + squareroots of the eigenvalues of A' A or A A'. Here ' denotes the hermitian + transpose (i.e. transposition and conjugation). Householder bidiagonalization + and a variant of the QR algorithm is used. + + overview of the matrices : + + A : m*n A gets replaced by U + U : m*n U replaces A. If n>m then only the first m*m block of U is + non-zero. column-unitary: U' U = B + here B is a n*n matrix whose first min(m,n) diagonal + elements are 1 and all other elements are zero. + S : n*n diagonal matrix, only the diagonal elements are stored in + the array S. only the first min(m,n) diagonal elements are non-zero. + V : n*n unitary: V V' = V' V = 1 + + parameters: + A (input/output) On input, A contains a complex matrix of shape m*n. + On output, if calc_u is true A contains the column-unitary + matrix U; otherwise A is simply used as workspace and thus destroyed. + + V (input/output) if false, the matrix V is not calculated. otherwise + V must be a matrix of shape n*n. + + calc_u (input) If true, the matrix U is calculated and replaces A. + if false, U is not calculated and A is simply destroyed + + return value: + S an array of length n containing the singular values of A sorted by + decreasing magnitude. only the first min(m,n) elements are non-zero. + + This routine is a python translation of the fortran routine svd.f in the + software library EISPACK (see netlib.org) which itself is based on the + algol procedure svd described in: + - num. math. 14, 403-420(1970) by golub and reinsch. + - wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971). + + """ + + m, n = A.rows, A.cols + + S = ctx.zeros(n, 1) + + # work is a temporary array of size n + work = ctx.zeros(n, 1) + lbeta = ctx.zeros(n, 1) + rbeta = ctx.zeros(n, 1) + dwork = ctx.zeros(n, 1) + + g = scale = anorm = 0 + maxits = 3 * ctx.dps + + for i in xrange(n): # householder reduction to bidiagonal form + dwork[i] = scale * g # dwork are the side-diagonal elements + g = s = scale = 0 + if i < m: + for k in xrange(i, m): + scale += ctx.fabs(ctx.re(A[k,i])) + ctx.fabs(ctx.im(A[k,i])) + if scale != 0: + for k in xrange(i, m): + A[k,i] /= scale + ar = ctx.re(A[k,i]) + ai = ctx.im(A[k,i]) + s += ar * ar + ai * ai + f = A[i,i] + g = -ctx.sqrt(s) + if ctx.re(f) < 0: + beta = -g - ctx.conj(f) + g = -g + else: + beta = -g + ctx.conj(f) + beta /= ctx.conj(beta) + beta += 1 + h = 2 * (ctx.re(f) * g - s) + A[i,i] = f - g + beta /= h + lbeta[i] = (beta / scale) / scale + for j in xrange(i+1, n): + s = 0 + for k in xrange(i, m): + s += ctx.conj(A[k,i]) * A[k,j] + f = beta * s + for k in xrange(i, m): + A[k,j] += f * A[k,i] + for k in xrange(i, m): + A[k,i] *= scale + + S[i] = scale * g # S are the diagonal elements + g = s = scale = 0 + + if i < m and i != n - 1: + for k in xrange(i+1, n): + scale += ctx.fabs(ctx.re(A[i,k])) + ctx.fabs(ctx.im(A[i,k])) + if scale: + for k in xrange(i+1, n): + A[i,k] /= scale + ar = ctx.re(A[i,k]) + ai = ctx.im(A[i,k]) + s += ar * ar + ai * ai + f = A[i,i+1] + g = -ctx.sqrt(s) + if ctx.re(f) < 0: + beta = -g - ctx.conj(f) + g = -g + else: + beta = -g + ctx.conj(f) + + beta /= ctx.conj(beta) + beta += 1 + + h = 2 * (ctx.re(f) * g - s) + A[i,i+1] = f - g + + beta /= h + rbeta[i] = (beta / scale) / scale + + for k in xrange(i+1, n): + work[k] = A[i, k] + + for j in xrange(i+1, m): + s = 0 + for k in xrange(i+1, n): + s += ctx.conj(A[i,k]) * A[j,k] + f = s * beta + for k in xrange(i+1,n): + A[j,k] += f * work[k] + + for k in xrange(i+1, n): + A[i,k] *= scale + + anorm = max(anorm,ctx.fabs(S[i]) + ctx.fabs(dwork[i])) + + if not isinstance(V, bool): + for i in xrange(n-2, -1, -1): # accumulation of right hand transformations + V[i+1,i+1] = 1 + + if dwork[i+1] != 0: + f = ctx.conj(rbeta[i]) + for j in xrange(i+1, n): + V[i,j] = A[i,j] * f + for j in xrange(i+1, n): + s = 0 + for k in xrange(i+1, n): + s += ctx.conj(A[i,k]) * V[j,k] + for k in xrange(i+1, n): + V[j,k] += s * V[i,k] + + for j in xrange(i+1,n): + V[j,i] = V[i,j] = 0 + + V[0,0] = 1 + + if m < n : minnm = m + else : minnm = n + + if calc_u: + for i in xrange(minnm-1, -1, -1): # accumulation of left hand transformations + g = S[i] + for j in xrange(i+1, n): + A[i,j] = 0 + if g != 0: + g = 1 / g + for j in xrange(i+1, n): + s = 0 + for k in xrange(i+1, m): + s += ctx.conj(A[k,i]) * A[k,j] + f = s * ctx.conj(lbeta[i]) + for k in xrange(i, m): + A[k,j] += f * A[k,i] + for j in xrange(i, m): + A[j,i] *= g + else: + for j in xrange(i, m): + A[j,i] = 0 + A[i,i] += 1 + + for k in xrange(n-1, -1, -1): + # diagonalization of the bidiagonal form: + # loop over singular values, and over allowed itations + + its = 0 + while 1: + its += 1 + flag = True + + for l in xrange(k, -1, -1): + nm = l - 1 + + if ctx.fabs(dwork[l]) + anorm == anorm: + flag = False + break + + if ctx.fabs(S[nm]) + anorm == anorm: + break + + if flag: + c = 0 + s = 1 + for i in xrange(l, k+1): + f = s * dwork[i] + dwork[i] *= c + if ctx.fabs(f) + anorm == anorm: + break + g = S[i] + h = ctx.hypot(f, g) + S[i] = h + h = 1 / h + c = g * h + s = -f * h + + if calc_u: + for j in xrange(m): + y = A[j,nm] + z = A[j,i] + A[j,nm]= y * c + z * s + A[j,i] = z * c - y * s + + z = S[k] + + if l == k: # convergence + if z < 0: # singular value is made nonnegative + S[k] = -z + if not isinstance(V, bool): + for j in xrange(n): + V[k,j] = -V[k,j] + break + + if its >= maxits: + raise RuntimeError("svd: no convergence to an eigenvalue after %d iterations" % its) + + x = S[l] # shift from bottom 2 by 2 minor + nm = k-1 + y = S[nm] + g = dwork[nm] + h = dwork[k] + f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2 * h * y) + g = ctx.hypot(f, 1) + if f >=0: f = (( x - z) *( x + z) + h *((y / (f + g)) - h)) / x + else: f = (( x - z) *( x + z) + h *((y / (f - g)) - h)) / x + + c = s = 1 # next qt transformation + + for j in xrange(l, nm + 1): + g = dwork[j+1] + y = S[j+1] + h = s * g + g = c * g + z = ctx.hypot(f, h) + dwork[j] = z + c = f / z + s = h / z + f = x * c + g * s + g = g * c - x * s + h = y * s + y *= c + if not isinstance(V, bool): + for jj in xrange(n): + x = V[j ,jj] + z = V[j+1,jj] + V[j ,jj]= x * c + z * s + V[j+1,jj ]= z * c - x * s + z = ctx.hypot(f, h) + S[j] = z + if z != 0: # rotation can be arbitray if z=0 + z = 1 / z + c = f * z + s = h * z + f = c * g + s * y + x = c * y - s * g + if calc_u: + for jj in xrange(m): + y = A[jj,j ] + z = A[jj,j+1] + A[jj,j ]= y * c + z * s + A[jj,j+1 ]= z * c - y * s + + dwork[l] = 0 + dwork[k] = f + S[k] = x + + ########################## + + # Sort singular values into decreasing order (bubble-sort) + + for i in xrange(n): + imax = i + s = ctx.fabs(S[i]) # s is the current maximal element + + for j in xrange(i + 1, n): + c = ctx.fabs(S[j]) + if c > s: + s = c + imax = j + + if imax != i: + # swap singular values + + z = S[i] + S[i] = S[imax] + S[imax] = z + + if calc_u: + for j in xrange(m): + z = A[j,i] + A[j,i] = A[j,imax] + A[j,imax] = z + + if not isinstance(V, bool): + for j in xrange(n): + z = V[i,j] + V[i,j] = V[imax,j] + V[imax,j] = z + + return S + +################################################################################################## + +@defun +def svd_r(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal matrices U and V are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the transpose. The diagonal elements of S are the singular + values of A, i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a real matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an orthogonal matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an orthogonal matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + examples: + + >>> from mpmath import mp + >>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) + >>> S = mp.svd_r(A, compute_uv = False) + >>> print(S) + [6.0] + [3.0] + [1.0] + + >>> U, S, V = mp.svd_r(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + + see also: svd, svd_c + """ + + m, n = A.rows, A.cols + + if not compute_uv: + if not overwrite_a: + A = A.copy() + S = svd_r_raw(ctx, A, V = False, calc_u = False) + S = S[:min(m,n)] + return S + + if full_matrices and n < m: + V = ctx.zeros(m, m) + A0 = ctx.zeros(m, m) + A0[:,:n] = A + S = svd_r_raw(ctx, A0, V, calc_u = True) + + S = S[:n] + V = V[:n,:n] + + return (A0, S, V) + else: + if not overwrite_a: + A = A.copy() + V = ctx.zeros(n, n) + S = svd_r_raw(ctx, A, V, calc_u = True) + + if n > m: + if full_matrices == False: + V = V[:m,:] + + S = S[:m] + A = A[:,:m] + + return (A, S, V) + +############################## + +@defun +def svd_c(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + This routine computes the singular value decomposition of a matrix A. + Given A, two unitary matrices U and V are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the hermitian transpose (i.e. transposition and complex + conjugation). The diagonal elements of S are the singular values of A, + i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a complex matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an unitary matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an unitary matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + example: + >>> from mpmath import mp + >>> A = mp.matrix([[-2j, -1-3j, -2+2j], [2-2j, -1-3j, 1], [-3+1j,-2j,0]]) + >>> S = mp.svd_c(A, compute_uv = False) + >>> print(mp.chop(S - mp.matrix([mp.sqrt(34), mp.sqrt(15), mp.sqrt(6)]))) + [0.0] + [0.0] + [0.0] + + >>> U, S, V = mp.svd_c(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + see also: svd, svd_r + """ + + m, n = A.rows, A.cols + + if not compute_uv: + if not overwrite_a: + A = A.copy() + S = svd_c_raw(ctx, A, V = False, calc_u = False) + S = S[:min(m,n)] + return S + + if full_matrices and n < m: + V = ctx.zeros(m, m) + A0 = ctx.zeros(m, m) + A0[:,:n] = A + S = svd_c_raw(ctx, A0, V, calc_u = True) + + S = S[:n] + V = V[:n,:n] + + return (A0, S, V) + else: + if not overwrite_a: + A = A.copy() + V = ctx.zeros(n, n) + S = svd_c_raw(ctx, A, V, calc_u = True) + + if n > m: + if full_matrices == False: + V = V[:m,:] + + S = S[:m] + A = A[:,:m] + + return (A, S, V) + +@defun +def svd(ctx, A, full_matrices = False, compute_uv = True, overwrite_a = False): + """ + "svd" is a unified interface for "svd_r" and "svd_c". Depending on + whether A is real or complex the appropriate function is called. + + This routine computes the singular value decomposition of a matrix A. + Given A, two orthogonal (A real) or unitary (A complex) matrices U and V + are calculated such that + + A = U S V and U' U = 1 and V V' = 1 + + where S is a suitable shaped matrix whose off-diagonal elements are zero. + Here ' denotes the hermitian transpose (i.e. transposition and complex + conjugation). The diagonal elements of S are the singular values of A, + i.e. the squareroots of the eigenvalues of A' A or A A'. + + input: + A : a real or complex matrix of shape (m, n) + full_matrices : if true, U and V are of shape (m, m) and (n, n). + if false, U and V are of shape (m, min(m, n)) and (min(m, n), n). + compute_uv : if true, U and V are calculated. if false, only S is calculated. + overwrite_a : if true, allows modification of A which may improve + performance. if false, A is not modified. + + output: + U : an orthogonal or unitary matrix: U' U = 1. if full_matrices is true, U is of + shape (m, m). ortherwise it is of shape (m, min(m, n)). + + S : an array of length min(m, n) containing the singular values of A sorted by + decreasing magnitude. + + V : an orthogonal or unitary matrix: V V' = 1. if full_matrices is true, V is of + shape (n, n). ortherwise it is of shape (min(m, n), n). + + return value: + + S if compute_uv is false + (U, S, V) if compute_uv is true + + overview of the matrices: + + full_matrices true: + A : m*n + U : m*m U' U = 1 + S as matrix : m*n + V : n*n V V' = 1 + + full_matrices false: + A : m*n + U : m*min(n,m) U' U = 1 + S as matrix : min(m,n)*min(m,n) + V : min(m,n)*n V V' = 1 + + examples: + + >>> from mpmath import mp + >>> A = mp.matrix([[2, -2, -1], [3, 4, -2], [-2, -2, 0]]) + >>> S = mp.svd(A, compute_uv = False) + >>> print(S) + [6.0] + [3.0] + [1.0] + + >>> U, S, V = mp.svd(A) + >>> print(mp.chop(A - U * mp.diag(S) * V)) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + see also: svd_r, svd_c + """ + + iscomplex = any(type(x) is ctx.mpc for x in A) + + if iscomplex: + return ctx.svd_c(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) + else: + return ctx.svd_r(A, full_matrices = full_matrices, compute_uv = compute_uv, overwrite_a = overwrite_a) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/linalg.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/linalg.py new file mode 100644 index 0000000000000000000000000000000000000000..e2fe643e809822e3d05a52b73c965edb622f9af9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/linalg.py @@ -0,0 +1,790 @@ +""" +Linear algebra +-------------- + +Linear equations +................ + +Basic linear algebra is implemented; you can for example solve the linear +equation system:: + + x + 2*y = -10 + 3*x + 4*y = 10 + +using ``lu_solve``:: + + >>> from mpmath import * + >>> mp.pretty = False + >>> A = matrix([[1, 2], [3, 4]]) + >>> b = matrix([-10, 10]) + >>> x = lu_solve(A, b) + >>> x + matrix( + [['30.0'], + ['-20.0']]) + +If you don't trust the result, use ``residual`` to calculate the residual ||A*x-b||:: + + >>> residual(A, x, b) + matrix( + [['3.46944695195361e-18'], + ['3.46944695195361e-18']]) + >>> str(eps) + '2.22044604925031e-16' + +As you can see, the solution is quite accurate. The error is caused by the +inaccuracy of the internal floating point arithmetic. Though, it's even smaller +than the current machine epsilon, which basically means you can trust the +result. + +If you need more speed, use NumPy, or ``fp.lu_solve`` for a floating-point computation. + + >>> fp.lu_solve(A, b) # doctest: +ELLIPSIS + matrix(...) + +``lu_solve`` accepts overdetermined systems. It is usually not possible to solve +such systems, so the residual is minimized instead. Internally this is done +using Cholesky decomposition to compute a least squares approximation. This means +that that ``lu_solve`` will square the errors. If you can't afford this, use +``qr_solve`` instead. It is twice as slow but more accurate, and it calculates +the residual automatically. + + +Matrix factorization +.................... + +The function ``lu`` computes an explicit LU factorization of a matrix:: + + >>> P, L, U = lu(matrix([[0,2,3],[4,5,6],[7,8,9]])) + >>> print(P) + [0.0 0.0 1.0] + [1.0 0.0 0.0] + [0.0 1.0 0.0] + >>> print(L) + [ 1.0 0.0 0.0] + [ 0.0 1.0 0.0] + [0.571428571428571 0.214285714285714 1.0] + >>> print(U) + [7.0 8.0 9.0] + [0.0 2.0 3.0] + [0.0 0.0 0.214285714285714] + >>> print(P.T*L*U) + [0.0 2.0 3.0] + [4.0 5.0 6.0] + [7.0 8.0 9.0] + +Interval matrices +----------------- + +Matrices may contain interval elements. This allows one to perform +basic linear algebra operations such as matrix multiplication +and equation solving with rigorous error bounds:: + + >>> a = iv.matrix([['0.1','0.3','1.0'], + ... ['7.1','5.5','4.8'], + ... ['3.2','4.4','5.6']]) + >>> + >>> b = iv.matrix(['4','0.6','0.5']) + >>> c = iv.lu_solve(a, b) + >>> print(c) + [ [5.2582327113062568605927528666, 5.25823271130625686059275702219]] + [[-13.1550493962678375411635581388, -13.1550493962678375411635540152]] + [ [7.42069154774972557628979076189, 7.42069154774972557628979190734]] + >>> print(a*c) + [ [3.99999999999999999999999844904, 4.00000000000000000000000155096]] + [[0.599999999999999999999968898009, 0.600000000000000000000031763736]] + [[0.499999999999999999999979320485, 0.500000000000000000000020679515]] +""" + +# TODO: +# *implement high-level qr() +# *test unitvector +# *iterative solving + +from copy import copy + +from ..libmp.backend import xrange + +class LinearAlgebraMethods(object): + + def LU_decomp(ctx, A, overwrite=False, use_cache=True): + """ + LU-factorization of a n*n matrix using the Gauss algorithm. + Returns L and U in one matrix and the pivot indices. + + Use overwrite to specify whether A will be overwritten with L and U. + """ + if not A.rows == A.cols: + raise ValueError('need n*n matrix') + # get from cache if possible + if use_cache and isinstance(A, ctx.matrix) and A._LU: + return A._LU + if not overwrite: + orig = A + A = A.copy() + tol = ctx.absmin(ctx.mnorm(A,1) * ctx.eps) # each pivot element has to be bigger + n = A.rows + p = [None]*(n - 1) + for j in xrange(n - 1): + # pivoting, choose max(abs(reciprocal row sum)*abs(pivot element)) + biggest = 0 + for k in xrange(j, n): + s = ctx.fsum([ctx.absmin(A[k,l]) for l in xrange(j, n)]) + if ctx.absmin(s) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + current = 1/s * ctx.absmin(A[k,j]) + if current > biggest: # TODO: what if equal? + biggest = current + p[j] = k + # swap rows according to p + ctx.swap_row(A, j, p[j]) + if ctx.absmin(A[j,j]) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + # calculate elimination factors and add rows + for i in xrange(j + 1, n): + A[i,j] /= A[j,j] + for k in xrange(j + 1, n): + A[i,k] -= A[i,j]*A[j,k] + if ctx.absmin(A[n - 1,n - 1]) <= tol: + raise ZeroDivisionError('matrix is numerically singular') + # cache decomposition + if not overwrite and isinstance(orig, ctx.matrix): + orig._LU = (A, p) + return A, p + + def L_solve(ctx, L, b, p=None): + """ + Solve the lower part of a LU factorized matrix for y. + """ + if L.rows != L.cols: + raise RuntimeError("need n*n matrix") + n = L.rows + if len(b) != n: + raise ValueError("Value should be equal to n") + b = copy(b) + if p: # swap b according to p + for k in xrange(0, len(p)): + ctx.swap_row(b, k, p[k]) + # solve + for i in xrange(1, n): + for j in xrange(i): + b[i] -= L[i,j] * b[j] + return b + + def U_solve(ctx, U, y): + """ + Solve the upper part of a LU factorized matrix for x. + """ + if U.rows != U.cols: + raise RuntimeError("need n*n matrix") + n = U.rows + if len(y) != n: + raise ValueError("Value should be equal to n") + x = copy(y) + for i in xrange(n - 1, -1, -1): + for j in xrange(i + 1, n): + x[i] -= U[i,j] * x[j] + x[i] /= U[i,i] + return x + + def lu_solve(ctx, A, b, **kwargs): + """ + Ax = b => x + + Solve a determined or overdetermined linear equations system. + Fast LU decomposition is used, which is less accurate than QR decomposition + (especially for overdetermined systems), but it's twice as efficient. + Use qr_solve if you want more precision or have to solve a very ill- + conditioned system. + + If you specify real=True, it does not check for overdeterminded complex + systems. + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows < A.cols: + raise ValueError('cannot solve underdetermined system') + if A.rows > A.cols: + # use least-squares method if overdetermined + # (this increases errors) + AH = A.H + A = AH * A + b = AH * b + if (kwargs.get('real', False) or + not sum(type(i) is ctx.mpc for i in A)): + # TODO: necessary to check also b? + x = ctx.cholesky_solve(A, b) + else: + x = ctx.lu_solve(A, b) + else: + # LU factorization + A, p = ctx.LU_decomp(A) + b = ctx.L_solve(A, b, p) + x = ctx.U_solve(A, b) + finally: + ctx.prec = prec + return x + + def improve_solution(ctx, A, x, b, maxsteps=1): + """ + Improve a solution to a linear equation system iteratively. + + This re-uses the LU decomposition and is thus cheap. + Usually 3 up to 4 iterations are giving the maximal improvement. + """ + if A.rows != A.cols: + raise RuntimeError("need n*n matrix") # TODO: really? + for _ in xrange(maxsteps): + r = ctx.residual(A, x, b) + if ctx.norm(r, 2) < 10*ctx.eps: + break + # this uses cached LU decomposition and is thus cheap + dx = ctx.lu_solve(A, -r) + x += dx + return x + + def lu(ctx, A): + """ + A -> P, L, U + + LU factorisation of a square matrix A. L is the lower, U the upper part. + P is the permutation matrix indicating the row swaps. + + P*A = L*U + + If you need efficiency, use the low-level method LU_decomp instead, it's + much more memory efficient. + """ + # get factorization + A, p = ctx.LU_decomp(A) + n = A.rows + L = ctx.matrix(n) + U = ctx.matrix(n) + for i in xrange(n): + for j in xrange(n): + if i > j: + L[i,j] = A[i,j] + elif i == j: + L[i,j] = 1 + U[i,j] = A[i,j] + else: + U[i,j] = A[i,j] + # calculate permutation matrix + P = ctx.eye(n) + for k in xrange(len(p)): + ctx.swap_row(P, k, p[k]) + return P, L, U + + def unitvector(ctx, n, i): + """ + Return the i-th n-dimensional unit vector. + """ + assert 0 < i <= n, 'this unit vector does not exist' + return [ctx.zero]*(i-1) + [ctx.one] + [ctx.zero]*(n-i) + + def inverse(ctx, A, **kwargs): + """ + Calculate the inverse of a matrix. + + If you want to solve an equation system Ax = b, it's recommended to use + solve(A, b) instead, it's about 3 times more efficient. + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A + A = ctx.matrix(A, **kwargs).copy() + n = A.rows + # get LU factorisation + A, p = ctx.LU_decomp(A) + cols = [] + # calculate unit vectors and solve corresponding system to get columns + for i in xrange(1, n + 1): + e = ctx.unitvector(n, i) + y = ctx.L_solve(A, e, p) + cols.append(ctx.U_solve(A, y)) + # convert columns to matrix + inv = [] + for i in xrange(n): + row = [] + for j in xrange(n): + row.append(cols[j][i]) + inv.append(row) + result = ctx.matrix(inv, **kwargs) + finally: + ctx.prec = prec + return result + + def householder(ctx, A): + """ + (A|b) -> H, p, x, res + + (A|b) is the coefficient matrix with left hand side of an optionally + overdetermined linear equation system. + H and p contain all information about the transformation matrices. + x is the solution, res the residual. + """ + if not isinstance(A, ctx.matrix): + raise TypeError("A should be a type of ctx.matrix") + m = A.rows + n = A.cols + if m < n - 1: + raise RuntimeError("Columns should not be less than rows") + # calculate Householder matrix + p = [] + for j in xrange(0, n - 1): + s = ctx.fsum(abs(A[i,j])**2 for i in xrange(j, m)) + if not abs(s) > ctx.eps: + raise ValueError('matrix is numerically singular') + p.append(-ctx.sign(ctx.re(A[j,j])) * ctx.sqrt(s)) + kappa = ctx.one / (s - p[j] * A[j,j]) + A[j,j] -= p[j] + for k in xrange(j+1, n): + y = ctx.fsum(ctx.conj(A[i,j]) * A[i,k] for i in xrange(j, m)) * kappa + for i in xrange(j, m): + A[i,k] -= A[i,j] * y + # solve Rx = c1 + x = [A[i,n - 1] for i in xrange(n - 1)] + for i in xrange(n - 2, -1, -1): + x[i] -= ctx.fsum(A[i,j] * x[j] for j in xrange(i + 1, n - 1)) + x[i] /= p[i] + # calculate residual + if not m == n - 1: + r = [A[m-1-i, n-1] for i in xrange(m - n + 1)] + else: + # determined system, residual should be 0 + r = [0]*m # maybe a bad idea, changing r[i] will change all elements + return A, p, x, r + + #def qr(ctx, A): + # """ + # A -> Q, R + # + # QR factorisation of a square matrix A using Householder decomposition. + # Q is orthogonal, this leads to very few numerical errors. + # + # A = Q*R + # """ + # H, p, x, res = householder(A) + # TODO: implement this + + def residual(ctx, A, x, b, **kwargs): + """ + Calculate the residual of a solution to a linear equation system. + + r = A*x - b for A*x = b + """ + oldprec = ctx.prec + try: + ctx.prec *= 2 + A, x, b = ctx.matrix(A, **kwargs), ctx.matrix(x, **kwargs), ctx.matrix(b, **kwargs) + return A*x - b + finally: + ctx.prec = oldprec + + def qr_solve(ctx, A, b, norm=None, **kwargs): + """ + Ax = b => x, ||Ax - b|| + + Solve a determined or overdetermined linear equations system and + calculate the norm of the residual (error). + QR decomposition using Householder factorization is applied, which gives very + accurate results even for ill-conditioned matrices. qr_solve is twice as + efficient. + """ + if norm is None: + norm = ctx.norm + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows < A.cols: + raise ValueError('cannot solve underdetermined system') + H, p, x, r = ctx.householder(ctx.extend(A, b)) + res = ctx.norm(r) + # calculate residual "manually" for determined systems + if res == 0: + res = ctx.norm(ctx.residual(A, x, b)) + return ctx.matrix(x, **kwargs), res + finally: + ctx.prec = prec + + def cholesky(ctx, A, tol=None): + r""" + Cholesky decomposition of a symmetric positive-definite matrix `A`. + Returns a lower triangular matrix `L` such that `A = L \times L^T`. + More generally, for a complex Hermitian positive-definite matrix, + a Cholesky decomposition satisfying `A = L \times L^H` is returned. + + The Cholesky decomposition can be used to solve linear equation + systems twice as efficiently as LU decomposition, or to + test whether `A` is positive-definite. + + The optional parameter ``tol`` determines the tolerance for + verifying positive-definiteness. + + **Examples** + + Cholesky decomposition of a positive-definite symmetric matrix:: + + >>> from mpmath import * + >>> mp.dps = 25; mp.pretty = True + >>> A = eye(3) + hilbert(3) + >>> nprint(A) + [ 2.0 0.5 0.333333] + [ 0.5 1.33333 0.25] + [0.333333 0.25 1.2] + >>> L = cholesky(A) + >>> nprint(L) + [ 1.41421 0.0 0.0] + [0.353553 1.09924 0.0] + [0.235702 0.15162 1.05899] + >>> chop(A - L*L.T) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + Cholesky decomposition of a Hermitian matrix:: + + >>> A = eye(3) + matrix([[0,0.25j,-0.5j],[-0.25j,0,0],[0.5j,0,0]]) + >>> L = cholesky(A) + >>> nprint(L) + [ 1.0 0.0 0.0] + [(0.0 - 0.25j) (0.968246 + 0.0j) 0.0] + [ (0.0 + 0.5j) (0.129099 + 0.0j) (0.856349 + 0.0j)] + >>> chop(A - L*L.H) + [0.0 0.0 0.0] + [0.0 0.0 0.0] + [0.0 0.0 0.0] + + Attempted Cholesky decomposition of a matrix that is not positive + definite:: + + >>> A = -eye(3) + hilbert(3) + >>> L = cholesky(A) + Traceback (most recent call last): + ... + ValueError: matrix is not positive-definite + + **References** + + 1. [Wikipedia]_ http://en.wikipedia.org/wiki/Cholesky_decomposition + + """ + if not isinstance(A, ctx.matrix): + raise RuntimeError("A should be a type of ctx.matrix") + if not A.rows == A.cols: + raise ValueError('need n*n matrix') + if tol is None: + tol = +ctx.eps + n = A.rows + L = ctx.matrix(n) + for j in xrange(n): + c = ctx.re(A[j,j]) + if abs(c-A[j,j]) > tol: + raise ValueError('matrix is not Hermitian') + s = c - ctx.fsum((L[j,k] for k in xrange(j)), + absolute=True, squared=True) + if s < tol: + raise ValueError('matrix is not positive-definite') + L[j,j] = ctx.sqrt(s) + for i in xrange(j, n): + it1 = (L[i,k] for k in xrange(j)) + it2 = (L[j,k] for k in xrange(j)) + t = ctx.fdot(it1, it2, conjugate=True) + L[i,j] = (A[i,j] - t) / L[j,j] + return L + + def cholesky_solve(ctx, A, b, **kwargs): + """ + Ax = b => x + + Solve a symmetric positive-definite linear equation system. + This is twice as efficient as lu_solve. + + Typical use cases: + * A.T*A + * Hessian matrix + * differential equations + """ + prec = ctx.prec + try: + ctx.prec += 10 + # do not overwrite A nor b + A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy() + if A.rows != A.cols: + raise ValueError('can only solve determined system') + # Cholesky factorization + L = ctx.cholesky(A) + # solve + n = L.rows + if len(b) != n: + raise ValueError("Value should be equal to n") + for i in xrange(n): + b[i] -= ctx.fsum(L[i,j] * b[j] for j in xrange(i)) + b[i] /= L[i,i] + x = ctx.U_solve(L.T, b) + return x + finally: + ctx.prec = prec + + def det(ctx, A): + """ + Calculate the determinant of a matrix. + """ + prec = ctx.prec + try: + # do not overwrite A + A = ctx.matrix(A).copy() + # use LU factorization to calculate determinant + try: + R, p = ctx.LU_decomp(A) + except ZeroDivisionError: + return 0 + z = 1 + for i, e in enumerate(p): + if i != e: + z *= -1 + for i in xrange(A.rows): + z *= R[i,i] + return z + finally: + ctx.prec = prec + + def cond(ctx, A, norm=None): + """ + Calculate the condition number of a matrix using a specified matrix norm. + + The condition number estimates the sensitivity of a matrix to errors. + Example: small input errors for ill-conditioned coefficient matrices + alter the solution of the system dramatically. + + For ill-conditioned matrices it's recommended to use qr_solve() instead + of lu_solve(). This does not help with input errors however, it just avoids + to add additional errors. + + Definition: cond(A) = ||A|| * ||A**-1|| + """ + if norm is None: + norm = lambda x: ctx.mnorm(x,1) + return norm(A) * norm(ctx.inverse(A)) + + def lu_solve_mat(ctx, a, b): + """Solve a * x = b where a and b are matrices.""" + r = ctx.matrix(a.rows, b.cols) + for i in range(b.cols): + c = ctx.lu_solve(a, b.column(i)) + for j in range(len(c)): + r[j, i] = c[j] + return r + + def qr(ctx, A, mode = 'full', edps = 10): + """ + Compute a QR factorization $A = QR$ where + A is an m x n matrix of real or complex numbers where m >= n + + mode has following meanings: + (1) mode = 'raw' returns two matrixes (A, tau) in the + internal format used by LAPACK + (2) mode = 'skinny' returns the leading n columns of Q + and n rows of R + (3) Any other value returns the leading m columns of Q + and m rows of R + + edps is the increase in mp precision used for calculations + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15 + >>> mp.pretty = True + >>> A = matrix([[1, 2], [3, 4], [1, 1]]) + >>> Q, R = qr(A) + >>> Q + [-0.301511344577764 0.861640436855329 0.408248290463863] + [-0.904534033733291 -0.123091490979333 -0.408248290463863] + [-0.301511344577764 -0.492365963917331 0.816496580927726] + >>> R + [-3.3166247903554 -4.52267016866645] + [ 0.0 0.738548945875996] + [ 0.0 0.0] + >>> Q * R + [1.0 2.0] + [3.0 4.0] + [1.0 1.0] + >>> chop(Q.T * Q) + [1.0 0.0 0.0] + [0.0 1.0 0.0] + [0.0 0.0 1.0] + >>> B = matrix([[1+0j, 2-3j], [3+j, 4+5j]]) + >>> Q, R = qr(B) + >>> nprint(Q) + [ (-0.301511 + 0.0j) (0.0695795 - 0.95092j)] + [(-0.904534 - 0.301511j) (-0.115966 + 0.278318j)] + >>> nprint(R) + [(-3.31662 + 0.0j) (-5.72872 - 2.41209j)] + [ 0.0 (3.91965 + 0.0j)] + >>> Q * R + [(1.0 + 0.0j) (2.0 - 3.0j)] + [(3.0 + 1.0j) (4.0 + 5.0j)] + >>> chop(Q.T * Q.conjugate()) + [1.0 0.0] + [0.0 1.0] + + """ + + # check values before continuing + assert isinstance(A, ctx.matrix) + m = A.rows + n = A.cols + assert n >= 0 + assert m >= n + assert edps >= 0 + + # check for complex data type + cmplx = any(type(x) is ctx.mpc for x in A) + + # temporarily increase the precision and initialize + with ctx.extradps(edps): + tau = ctx.matrix(n,1) + A = A.copy() + + # --------------- + # FACTOR MATRIX A + # --------------- + if cmplx: + one = ctx.mpc('1.0', '0.0') + zero = ctx.mpc('0.0', '0.0') + rzero = ctx.mpf('0.0') + + # main loop to factor A (complex) + for j in xrange(0, n): + alpha = A[j,j] + alphr = ctx.re(alpha) + alphi = ctx.im(alpha) + + if (m-j) >= 2: + xnorm = ctx.fsum( A[i,j]*ctx.conj(A[i,j]) for i in xrange(j+1, m) ) + xnorm = ctx.re( ctx.sqrt(xnorm) ) + else: + xnorm = rzero + + if (xnorm == rzero) and (alphi == rzero): + tau[j] = zero + continue + + if alphr < rzero: + beta = ctx.sqrt(alphr**2 + alphi**2 + xnorm**2) + else: + beta = -ctx.sqrt(alphr**2 + alphi**2 + xnorm**2) + + tau[j] = ctx.mpc( (beta - alphr) / beta, -alphi / beta ) + t = -ctx.conj(tau[j]) + za = one / (alpha - beta) + + for i in xrange(j+1, m): + A[i,j] *= za + + A[j,j] = one + for k in xrange(j+1, n): + y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in xrange(j, m)) + temp = t * ctx.conj(y) + for i in xrange(j, m): + A[i,k] += A[i,j] * temp + + A[j,j] = ctx.mpc(beta, '0.0') + else: + one = ctx.mpf('1.0') + zero = ctx.mpf('0.0') + + # main loop to factor A (real) + for j in xrange(0, n): + alpha = A[j,j] + + if (m-j) > 2: + xnorm = ctx.fsum( (A[i,j])**2 for i in xrange(j+1, m) ) + xnorm = ctx.sqrt(xnorm) + elif (m-j) == 2: + xnorm = abs( A[m-1,j] ) + else: + xnorm = zero + + if xnorm == zero: + tau[j] = zero + continue + + if alpha < zero: + beta = ctx.sqrt(alpha**2 + xnorm**2) + else: + beta = -ctx.sqrt(alpha**2 + xnorm**2) + + tau[j] = (beta - alpha) / beta + t = -tau[j] + da = one / (alpha - beta) + + for i in xrange(j+1, m): + A[i,j] *= da + + A[j,j] = one + for k in xrange(j+1, n): + y = ctx.fsum( A[i,j] * A[i,k] for i in xrange(j, m) ) + temp = t * y + for i in xrange(j,m): + A[i,k] += A[i,j] * temp + + A[j,j] = beta + + # return factorization in same internal format as LAPACK + if (mode == 'raw') or (mode == 'RAW'): + return A, tau + + # ---------------------------------- + # FORM Q USING BACKWARD ACCUMULATION + # ---------------------------------- + + # form R before the values are overwritten + R = A.copy() + for j in xrange(0, n): + for i in xrange(j+1, m): + R[i,j] = zero + + # set the value of p (number of columns of Q to return) + p = m + if (mode == 'skinny') or (mode == 'SKINNY'): + p = n + + # add columns to A if needed and initialize + A.cols += (p-n) + for j in xrange(0, p): + A[j,j] = one + for i in xrange(0, j): + A[i,j] = zero + + # main loop to form Q + for j in xrange(n-1, -1, -1): + t = -tau[j] + A[j,j] += t + + for k in xrange(j+1, p): + if cmplx: + y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in xrange(j+1, m)) + temp = t * ctx.conj(y) + else: + y = ctx.fsum(A[i,j] * A[i,k] for i in xrange(j+1, m)) + temp = t * y + A[j,k] = temp + for i in xrange(j+1, m): + A[i,k] += A[i,j] * temp + + for i in xrange(j+1, m): + A[i, j] *= t + + return A, R[0:p,0:n] + + # ------------------ + # END OF FUNCTION QR + # ------------------ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/matrices.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..a97d5a9ca7e173195386dc7cb60860a826ab6a97 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/matrices/matrices.py @@ -0,0 +1,1005 @@ +from ..libmp.backend import xrange +import warnings + +# TODO: interpret list as vectors (for multiplication) + +rowsep = '\n' +colsep = ' ' + +class _matrix(object): + """ + Numerical matrix. + + Specify the dimensions or the data as a nested list. + Elements default to zero. + Use a flat list to create a column vector easily. + + The datatype of the context (mpf for mp, mpi for iv, and float for fp) is used to store the data. + + Creating matrices + ----------------- + + Matrices in mpmath are implemented using dictionaries. Only non-zero values + are stored, so it is cheap to represent sparse matrices. + + The most basic way to create one is to use the ``matrix`` class directly. + You can create an empty matrix specifying the dimensions: + + >>> from mpmath import * + >>> mp.dps = 15 + >>> matrix(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> matrix(2, 3) + matrix( + [['0.0', '0.0', '0.0'], + ['0.0', '0.0', '0.0']]) + + Calling ``matrix`` with one dimension will create a square matrix. + + To access the dimensions of a matrix, use the ``rows`` or ``cols`` keyword: + + >>> A = matrix(3, 2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0'], + ['0.0', '0.0']]) + >>> A.rows + 3 + >>> A.cols + 2 + + You can also change the dimension of an existing matrix. This will set the + new elements to 0. If the new dimension is smaller than before, the + concerning elements are discarded: + + >>> A.rows = 2 + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + + Internally ``mpmathify`` is used every time an element is set. This + is done using the syntax A[row,column], counting from 0: + + >>> A = matrix(2) + >>> A[1,1] = 1 + 1j + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', mpc(real='1.0', imag='1.0')]]) + + A more comfortable way to create a matrix lets you use nested lists: + + >>> matrix([[1, 2], [3, 4]]) + matrix( + [['1.0', '2.0'], + ['3.0', '4.0']]) + + Convenient advanced functions are available for creating various standard + matrices, see ``zeros``, ``ones``, ``diag``, ``eye``, ``randmatrix`` and + ``hilbert``. + + Vectors + ....... + + Vectors may also be represented by the ``matrix`` class (with rows = 1 or cols = 1). + For vectors there are some things which make life easier. A column vector can + be created using a flat list, a row vectors using an almost flat nested list:: + + >>> matrix([1, 2, 3]) + matrix( + [['1.0'], + ['2.0'], + ['3.0']]) + >>> matrix([[1, 2, 3]]) + matrix( + [['1.0', '2.0', '3.0']]) + + Optionally vectors can be accessed like lists, using only a single index:: + + >>> x = matrix([1, 2, 3]) + >>> x[1] + mpf('2.0') + >>> x[1,0] + mpf('2.0') + + Other + ..... + + Like you probably expected, matrices can be printed:: + + >>> print randmatrix(3) # doctest:+SKIP + [ 0.782963853573023 0.802057689719883 0.427895717335467] + [0.0541876859348597 0.708243266653103 0.615134039977379] + [ 0.856151514955773 0.544759264818486 0.686210904770947] + + Use ``nstr`` or ``nprint`` to specify the number of digits to print:: + + >>> nprint(randmatrix(5), 3) # doctest:+SKIP + [2.07e-1 1.66e-1 5.06e-1 1.89e-1 8.29e-1] + [6.62e-1 6.55e-1 4.47e-1 4.82e-1 2.06e-2] + [4.33e-1 7.75e-1 6.93e-2 2.86e-1 5.71e-1] + [1.01e-1 2.53e-1 6.13e-1 3.32e-1 2.59e-1] + [1.56e-1 7.27e-2 6.05e-1 6.67e-2 2.79e-1] + + As matrices are mutable, you will need to copy them sometimes:: + + >>> A = matrix(2) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + >>> B = A.copy() + >>> B[0,0] = 1 + >>> B + matrix( + [['1.0', '0.0'], + ['0.0', '0.0']]) + >>> A + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + + Finally, it is possible to convert a matrix to a nested list. This is very useful, + as most Python libraries involving matrices or arrays (namely NumPy or SymPy) + support this format:: + + >>> B.tolist() + [[mpf('1.0'), mpf('0.0')], [mpf('0.0'), mpf('0.0')]] + + + Matrix operations + ----------------- + + You can add and subtract matrices of compatible dimensions:: + + >>> A = matrix([[1, 2], [3, 4]]) + >>> B = matrix([[-2, 4], [5, 9]]) + >>> A + B + matrix( + [['-1.0', '6.0'], + ['8.0', '13.0']]) + >>> A - B + matrix( + [['3.0', '-2.0'], + ['-2.0', '-5.0']]) + >>> A + ones(3) # doctest:+ELLIPSIS + Traceback (most recent call last): + ... + ValueError: incompatible dimensions for addition + + It is possible to multiply or add matrices and scalars. In the latter case the + operation will be done element-wise:: + + >>> A * 2 + matrix( + [['2.0', '4.0'], + ['6.0', '8.0']]) + >>> A / 4 + matrix( + [['0.25', '0.5'], + ['0.75', '1.0']]) + >>> A - 1 + matrix( + [['0.0', '1.0'], + ['2.0', '3.0']]) + + Of course you can perform matrix multiplication, if the dimensions are + compatible, using ``@`` (for Python >= 3.5) or ``*``. For clarity, ``@`` is + recommended (`PEP 465 `), because + the meaning of ``*`` is different in many other Python libraries such as NumPy. + + >>> A @ B # doctest:+SKIP + matrix( + [['8.0', '22.0'], + ['14.0', '48.0']]) + >>> A * B # same as A @ B + matrix( + [['8.0', '22.0'], + ['14.0', '48.0']]) + >>> matrix([[1, 2, 3]]) * matrix([[-6], [7], [-2]]) + matrix( + [['2.0']]) + + .. + COMMENT: TODO: the above "doctest:+SKIP" may be removed as soon as we + have dropped support for Python 3.5 and below. + + You can raise powers of square matrices:: + + >>> A**2 + matrix( + [['7.0', '10.0'], + ['15.0', '22.0']]) + + Negative powers will calculate the inverse:: + + >>> A**-1 + matrix( + [['-2.0', '1.0'], + ['1.5', '-0.5']]) + >>> A * A**-1 + matrix( + [['1.0', '1.0842021724855e-19'], + ['-2.16840434497101e-19', '1.0']]) + + + + Matrix transposition is straightforward:: + + >>> A = ones(2, 3) + >>> A + matrix( + [['1.0', '1.0', '1.0'], + ['1.0', '1.0', '1.0']]) + >>> A.T + matrix( + [['1.0', '1.0'], + ['1.0', '1.0'], + ['1.0', '1.0']]) + + Norms + ..... + + Sometimes you need to know how "large" a matrix or vector is. Due to their + multidimensional nature it's not possible to compare them, but there are + several functions to map a matrix or a vector to a positive real number, the + so called norms. + + For vectors the p-norm is intended, usually the 1-, the 2- and the oo-norm are + used. + + >>> x = matrix([-10, 2, 100]) + >>> norm(x, 1) + mpf('112.0') + >>> norm(x, 2) + mpf('100.5186549850325') + >>> norm(x, inf) + mpf('100.0') + + Please note that the 2-norm is the most used one, though it is more expensive + to calculate than the 1- or oo-norm. + + It is possible to generalize some vector norms to matrix norm:: + + >>> A = matrix([[1, -1000], [100, 50]]) + >>> mnorm(A, 1) + mpf('1050.0') + >>> mnorm(A, inf) + mpf('1001.0') + >>> mnorm(A, 'F') + mpf('1006.2310867787777') + + The last norm (the "Frobenius-norm") is an approximation for the 2-norm, which + is hard to calculate and not available. The Frobenius-norm lacks some + mathematical properties you might expect from a norm. + """ + + def __init__(self, *args, **kwargs): + self.__data = {} + # LU decompostion cache, this is useful when solving the same system + # multiple times, when calculating the inverse and when calculating the + # determinant + self._LU = None + if "force_type" in kwargs: + warnings.warn("The force_type argument was removed, it did not work" + " properly anyway. If you want to force floating-point or" + " interval computations, use the respective methods from `fp`" + " or `mp` instead, e.g., `fp.matrix()` or `iv.matrix()`." + " If you want to truncate values to integer, use .apply(int) instead.") + if isinstance(args[0], (list, tuple)): + if isinstance(args[0][0], (list, tuple)): + # interpret nested list as matrix + A = args[0] + self.__rows = len(A) + self.__cols = len(A[0]) + for i, row in enumerate(A): + for j, a in enumerate(row): + # note: this will call __setitem__ which will call self.ctx.convert() to convert the datatype. + self[i, j] = a + else: + # interpret list as row vector + v = args[0] + self.__rows = len(v) + self.__cols = 1 + for i, e in enumerate(v): + self[i, 0] = e + elif isinstance(args[0], int): + # create empty matrix of given dimensions + if len(args) == 1: + self.__rows = self.__cols = args[0] + else: + if not isinstance(args[1], int): + raise TypeError("expected int") + self.__rows = args[0] + self.__cols = args[1] + elif isinstance(args[0], _matrix): + A = args[0] + self.__rows = A._matrix__rows + self.__cols = A._matrix__cols + for i in xrange(A.__rows): + for j in xrange(A.__cols): + self[i, j] = A[i, j] + elif hasattr(args[0], 'tolist'): + A = self.ctx.matrix(args[0].tolist()) + self.__data = A._matrix__data + self.__rows = A._matrix__rows + self.__cols = A._matrix__cols + else: + raise TypeError('could not interpret given arguments') + + def apply(self, f): + """ + Return a copy of self with the function `f` applied elementwise. + """ + new = self.ctx.matrix(self.__rows, self.__cols) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[i,j] = f(self[i,j]) + return new + + def __nstr__(self, n=None, **kwargs): + # Build table of string representations of the elements + res = [] + # Track per-column max lengths for pretty alignment + maxlen = [0] * self.cols + for i in range(self.rows): + res.append([]) + for j in range(self.cols): + if n: + string = self.ctx.nstr(self[i,j], n, **kwargs) + else: + string = str(self[i,j]) + res[-1].append(string) + maxlen[j] = max(len(string), maxlen[j]) + # Patch strings together + for i, row in enumerate(res): + for j, elem in enumerate(row): + # Pad each element up to maxlen so the columns line up + row[j] = elem.rjust(maxlen[j]) + res[i] = "[" + colsep.join(row) + "]" + return rowsep.join(res) + + def __str__(self): + return self.__nstr__() + + def _toliststr(self, avoid_type=False): + """ + Create a list string from a matrix. + + If avoid_type: avoid multiple 'mpf's. + """ + # XXX: should be something like self.ctx._types + typ = self.ctx.mpf + s = '[' + for i in xrange(self.__rows): + s += '[' + for j in xrange(self.__cols): + if not avoid_type or not isinstance(self[i,j], typ): + a = repr(self[i,j]) + else: + a = "'" + str(self[i,j]) + "'" + s += a + ', ' + s = s[:-2] + s += '],\n ' + s = s[:-3] + s += ']' + return s + + def tolist(self): + """ + Convert the matrix to a nested list. + """ + return [[self[i,j] for j in range(self.__cols)] for i in range(self.__rows)] + + def __repr__(self): + if self.ctx.pretty: + return self.__str__() + s = 'matrix(\n' + s += self._toliststr(avoid_type=True) + ')' + return s + + def __get_element(self, key): + ''' + Fast extraction of the i,j element from the matrix + This function is for private use only because is unsafe: + 1. Does not check on the value of key it expects key to be a integer tuple (i,j) + 2. Does not check bounds + ''' + if key in self.__data: + return self.__data[key] + else: + return self.ctx.zero + + def __set_element(self, key, value): + ''' + Fast assignment of the i,j element in the matrix + This function is unsafe: + 1. Does not check on the value of key it expects key to be a integer tuple (i,j) + 2. Does not check bounds + 3. Does not check the value type + 4. Does not reset the LU cache + ''' + if value: # only store non-zeros + self.__data[key] = value + elif key in self.__data: + del self.__data[key] + + + def __getitem__(self, key): + ''' + Getitem function for mp matrix class with slice index enabled + it allows the following assingments + scalar to a slice of the matrix + B = A[:,2:6] + ''' + # Convert vector to matrix indexing + if isinstance(key, int) or isinstance(key,slice): + # only sufficent for vectors + if self.__rows == 1: + key = (0, key) + elif self.__cols == 1: + key = (key, 0) + else: + raise IndexError('insufficient indices for matrix') + + if isinstance(key[0],slice) or isinstance(key[1],slice): + + #Rows + if isinstance(key[0],slice): + #Check bounds + if (key[0].start is None or key[0].start >= 0) and \ + (key[0].stop is None or key[0].stop <= self.__rows+1): + # Generate indices + rows = xrange(*key[0].indices(self.__rows)) + else: + raise IndexError('Row index out of bounds') + else: + # Single row + rows = [key[0]] + + # Columns + if isinstance(key[1],slice): + # Check bounds + if (key[1].start is None or key[1].start >= 0) and \ + (key[1].stop is None or key[1].stop <= self.__cols+1): + # Generate indices + columns = xrange(*key[1].indices(self.__cols)) + else: + raise IndexError('Column index out of bounds') + + else: + # Single column + columns = [key[1]] + + # Create matrix slice + m = self.ctx.matrix(len(rows),len(columns)) + + # Assign elements to the output matrix + for i,x in enumerate(rows): + for j,y in enumerate(columns): + m.__set_element((i,j),self.__get_element((x,y))) + + return m + + else: + # single element extraction + if key[0] >= self.__rows or key[1] >= self.__cols: + raise IndexError('matrix index out of range') + if key in self.__data: + return self.__data[key] + else: + return self.ctx.zero + + def __setitem__(self, key, value): + # setitem function for mp matrix class with slice index enabled + # it allows the following assingments + # scalar to a slice of the matrix + # A[:,2:6] = 2.5 + # submatrix to matrix (the value matrix should be the same size as the slice size) + # A[3,:] = B where A is n x m and B is n x 1 + # Convert vector to matrix indexing + if isinstance(key, int) or isinstance(key,slice): + # only sufficent for vectors + if self.__rows == 1: + key = (0, key) + elif self.__cols == 1: + key = (key, 0) + else: + raise IndexError('insufficient indices for matrix') + # Slice indexing + if isinstance(key[0],slice) or isinstance(key[1],slice): + # Rows + if isinstance(key[0],slice): + # Check bounds + if (key[0].start is None or key[0].start >= 0) and \ + (key[0].stop is None or key[0].stop <= self.__rows+1): + # generate row indices + rows = xrange(*key[0].indices(self.__rows)) + else: + raise IndexError('Row index out of bounds') + else: + # Single row + rows = [key[0]] + # Columns + if isinstance(key[1],slice): + # Check bounds + if (key[1].start is None or key[1].start >= 0) and \ + (key[1].stop is None or key[1].stop <= self.__cols+1): + # Generate column indices + columns = xrange(*key[1].indices(self.__cols)) + else: + raise IndexError('Column index out of bounds') + else: + # Single column + columns = [key[1]] + # Assign slice with a scalar + if isinstance(value,self.ctx.matrix): + # Assign elements to matrix if input and output dimensions match + if len(rows) == value.rows and len(columns) == value.cols: + for i,x in enumerate(rows): + for j,y in enumerate(columns): + self.__set_element((x,y), value.__get_element((i,j))) + else: + raise ValueError('Dimensions do not match') + else: + # Assign slice with scalars + value = self.ctx.convert(value) + for i in rows: + for j in columns: + self.__set_element((i,j), value) + else: + # Single element assingment + # Check bounds + if key[0] >= self.__rows or key[1] >= self.__cols: + raise IndexError('matrix index out of range') + # Convert and store value + value = self.ctx.convert(value) + if value: # only store non-zeros + self.__data[key] = value + elif key in self.__data: + del self.__data[key] + + if self._LU: + self._LU = None + return + + def __iter__(self): + for i in xrange(self.__rows): + for j in xrange(self.__cols): + yield self[i,j] + + def __mul__(self, other): + if isinstance(other, self.ctx.matrix): + # dot multiplication + if self.__cols != other.__rows: + raise ValueError('dimensions not compatible for multiplication') + new = self.ctx.matrix(self.__rows, other.__cols) + self_zero = self.ctx.zero + self_get = self.__data.get + other_zero = other.ctx.zero + other_get = other.__data.get + for i in xrange(self.__rows): + for j in xrange(other.__cols): + new[i, j] = self.ctx.fdot((self_get((i,k), self_zero), other_get((k,j), other_zero)) + for k in xrange(other.__rows)) + return new + else: + # try scalar multiplication + new = self.ctx.matrix(self.__rows, self.__cols) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[i, j] = other * self[i, j] + return new + + def __matmul__(self, other): + return self.__mul__(other) + + def __rmul__(self, other): + # assume other is scalar and thus commutative + if isinstance(other, self.ctx.matrix): + raise TypeError("other should not be type of ctx.matrix") + return self.__mul__(other) + + def __pow__(self, other): + # avoid cyclic import problems + #from linalg import inverse + if not isinstance(other, int): + raise ValueError('only integer exponents are supported') + if not self.__rows == self.__cols: + raise ValueError('only powers of square matrices are defined') + n = other + if n == 0: + return self.ctx.eye(self.__rows) + if n < 0: + n = -n + neg = True + else: + neg = False + i = n + y = 1 + z = self.copy() + while i != 0: + if i % 2 == 1: + y = y * z + z = z*z + i = i // 2 + if neg: + y = self.ctx.inverse(y) + return y + + def __div__(self, other): + # assume other is scalar and do element-wise divison + assert not isinstance(other, self.ctx.matrix) + new = self.ctx.matrix(self.__rows, self.__cols) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[i,j] = self[i,j] / other + return new + + __truediv__ = __div__ + + def __add__(self, other): + if isinstance(other, self.ctx.matrix): + if not (self.__rows == other.__rows and self.__cols == other.__cols): + raise ValueError('incompatible dimensions for addition') + new = self.ctx.matrix(self.__rows, self.__cols) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[i,j] = self[i,j] + other[i,j] + return new + else: + # assume other is scalar and add element-wise + new = self.ctx.matrix(self.__rows, self.__cols) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[i,j] += self[i,j] + other + return new + + def __radd__(self, other): + return self.__add__(other) + + def __sub__(self, other): + if isinstance(other, self.ctx.matrix) and not (self.__rows == other.__rows + and self.__cols == other.__cols): + raise ValueError('incompatible dimensions for subtraction') + return self.__add__(other * (-1)) + + def __pos__(self): + """ + +M returns a copy of M, rounded to current working precision. + """ + return (+1) * self + + def __neg__(self): + return (-1) * self + + def __rsub__(self, other): + return -self + other + + def __eq__(self, other): + return self.__rows == other.__rows and self.__cols == other.__cols \ + and self.__data == other.__data + + def __len__(self): + if self.rows == 1: + return self.cols + elif self.cols == 1: + return self.rows + else: + return self.rows # do it like numpy + + def __getrows(self): + return self.__rows + + def __setrows(self, value): + for key in self.__data.copy(): + if key[0] >= value: + del self.__data[key] + self.__rows = value + + rows = property(__getrows, __setrows, doc='number of rows') + + def __getcols(self): + return self.__cols + + def __setcols(self, value): + for key in self.__data.copy(): + if key[1] >= value: + del self.__data[key] + self.__cols = value + + cols = property(__getcols, __setcols, doc='number of columns') + + def transpose(self): + new = self.ctx.matrix(self.__cols, self.__rows) + for i in xrange(self.__rows): + for j in xrange(self.__cols): + new[j,i] = self[i,j] + return new + + T = property(transpose) + + def conjugate(self): + return self.apply(self.ctx.conj) + + def transpose_conj(self): + return self.conjugate().transpose() + + H = property(transpose_conj) + + def copy(self): + new = self.ctx.matrix(self.__rows, self.__cols) + new.__data = self.__data.copy() + return new + + __copy__ = copy + + def column(self, n): + m = self.ctx.matrix(self.rows, 1) + for i in range(self.rows): + m[i] = self[i,n] + return m + +class MatrixMethods(object): + + def __init__(ctx): + # XXX: subclass + ctx.matrix = type('matrix', (_matrix,), {}) + ctx.matrix.ctx = ctx + ctx.matrix.convert = ctx.convert + + def eye(ctx, n, **kwargs): + """ + Create square identity matrix n x n. + """ + A = ctx.matrix(n, **kwargs) + for i in xrange(n): + A[i,i] = 1 + return A + + def diag(ctx, diagonal, **kwargs): + """ + Create square diagonal matrix using given list. + + Example: + >>> from mpmath import diag, mp + >>> mp.pretty = False + >>> diag([1, 2, 3]) + matrix( + [['1.0', '0.0', '0.0'], + ['0.0', '2.0', '0.0'], + ['0.0', '0.0', '3.0']]) + """ + A = ctx.matrix(len(diagonal), **kwargs) + for i in xrange(len(diagonal)): + A[i,i] = diagonal[i] + return A + + def zeros(ctx, *args, **kwargs): + """ + Create matrix m x n filled with zeros. + One given dimension will create square matrix n x n. + + Example: + >>> from mpmath import zeros, mp + >>> mp.pretty = False + >>> zeros(2) + matrix( + [['0.0', '0.0'], + ['0.0', '0.0']]) + """ + if len(args) == 1: + m = n = args[0] + elif len(args) == 2: + m = args[0] + n = args[1] + else: + raise TypeError('zeros expected at most 2 arguments, got %i' % len(args)) + A = ctx.matrix(m, n, **kwargs) + for i in xrange(m): + for j in xrange(n): + A[i,j] = 0 + return A + + def ones(ctx, *args, **kwargs): + """ + Create matrix m x n filled with ones. + One given dimension will create square matrix n x n. + + Example: + >>> from mpmath import ones, mp + >>> mp.pretty = False + >>> ones(2) + matrix( + [['1.0', '1.0'], + ['1.0', '1.0']]) + """ + if len(args) == 1: + m = n = args[0] + elif len(args) == 2: + m = args[0] + n = args[1] + else: + raise TypeError('ones expected at most 2 arguments, got %i' % len(args)) + A = ctx.matrix(m, n, **kwargs) + for i in xrange(m): + for j in xrange(n): + A[i,j] = 1 + return A + + def hilbert(ctx, m, n=None): + """ + Create (pseudo) hilbert matrix m x n. + One given dimension will create hilbert matrix n x n. + + The matrix is very ill-conditioned and symmetric, positive definite if + square. + """ + if n is None: + n = m + A = ctx.matrix(m, n) + for i in xrange(m): + for j in xrange(n): + A[i,j] = ctx.one / (i + j + 1) + return A + + def randmatrix(ctx, m, n=None, min=0, max=1, **kwargs): + """ + Create a random m x n matrix. + + All values are >= min and >> from mpmath import randmatrix + >>> randmatrix(2) # doctest:+SKIP + matrix( + [['0.53491598236191806', '0.57195669543302752'], + ['0.85589992269513615', '0.82444367501382143']]) + """ + if not n: + n = m + A = ctx.matrix(m, n, **kwargs) + for i in xrange(m): + for j in xrange(n): + A[i,j] = ctx.rand() * (max - min) + min + return A + + def swap_row(ctx, A, i, j): + """ + Swap row i with row j. + """ + if i == j: + return + if isinstance(A, ctx.matrix): + for k in xrange(A.cols): + A[i,k], A[j,k] = A[j,k], A[i,k] + elif isinstance(A, list): + A[i], A[j] = A[j], A[i] + else: + raise TypeError('could not interpret type') + + def extend(ctx, A, b): + """ + Extend matrix A with column b and return result. + """ + if not isinstance(A, ctx.matrix): + raise TypeError("A should be a type of ctx.matrix") + if A.rows != len(b): + raise ValueError("Value should be equal to len(b)") + A = A.copy() + A.cols += 1 + for i in xrange(A.rows): + A[i, A.cols-1] = b[i] + return A + + def norm(ctx, x, p=2): + r""" + Gives the entrywise `p`-norm of an iterable *x*, i.e. the vector norm + `\left(\sum_k |x_k|^p\right)^{1/p}`, for any given `1 \le p \le \infty`. + + Special cases: + + If *x* is not iterable, this just returns ``absmax(x)``. + + ``p=1`` gives the sum of absolute values. + + ``p=2`` is the standard Euclidean vector norm. + + ``p=inf`` gives the magnitude of the largest element. + + For *x* a matrix, ``p=2`` is the Frobenius norm. + For operator matrix norms, use :func:`~mpmath.mnorm` instead. + + You can use the string 'inf' as well as float('inf') or mpf('inf') + to specify the infinity norm. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = False + >>> x = matrix([-10, 2, 100]) + >>> norm(x, 1) + mpf('112.0') + >>> norm(x, 2) + mpf('100.5186549850325') + >>> norm(x, inf) + mpf('100.0') + + """ + try: + iter(x) + except TypeError: + return ctx.absmax(x) + if type(p) is not int: + p = ctx.convert(p) + if p == ctx.inf: + return max(ctx.absmax(i) for i in x) + elif p == 1: + return ctx.fsum(x, absolute=1) + elif p == 2: + return ctx.sqrt(ctx.fsum(x, absolute=1, squared=1)) + elif p > 1: + return ctx.nthroot(ctx.fsum(abs(i)**p for i in x), p) + else: + raise ValueError('p has to be >= 1') + + def mnorm(ctx, A, p=1): + r""" + Gives the matrix (operator) `p`-norm of A. Currently ``p=1`` and ``p=inf`` + are supported: + + ``p=1`` gives the 1-norm (maximal column sum) + + ``p=inf`` gives the `\infty`-norm (maximal row sum). + You can use the string 'inf' as well as float('inf') or mpf('inf') + + ``p=2`` (not implemented) for a square matrix is the usual spectral + matrix norm, i.e. the largest singular value. + + ``p='f'`` (or 'F', 'fro', 'Frobenius, 'frobenius') gives the + Frobenius norm, which is the elementwise 2-norm. The Frobenius norm is an + approximation of the spectral norm and satisfies + + .. math :: + + \frac{1}{\sqrt{\mathrm{rank}(A)}} \|A\|_F \le \|A\|_2 \le \|A\|_F + + The Frobenius norm lacks some mathematical properties that might + be expected of a norm. + + For general elementwise `p`-norms, use :func:`~mpmath.norm` instead. + + **Examples** + + >>> from mpmath import * + >>> mp.dps = 15; mp.pretty = False + >>> A = matrix([[1, -1000], [100, 50]]) + >>> mnorm(A, 1) + mpf('1050.0') + >>> mnorm(A, inf) + mpf('1001.0') + >>> mnorm(A, 'F') + mpf('1006.2310867787777') + + """ + A = ctx.matrix(A) + if type(p) is not int: + if type(p) is str and 'frobenius'.startswith(p.lower()): + return ctx.norm(A, 2) + p = ctx.convert(p) + m, n = A.rows, A.cols + if p == 1: + return max(ctx.fsum((A[i,j] for i in xrange(m)), absolute=1) for j in xrange(n)) + elif p == ctx.inf: + return max(ctx.fsum((A[i,j] for j in xrange(n)), absolute=1) for i in xrange(m)) + else: + raise NotImplementedError("matrix p-norm for arbitrary p") + +if __name__ == '__main__': + import doctest + doctest.testmod() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a116cc8520faa26cff0d65caef27326a7cfe49bd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_gamma.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_gamma.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..341992d270bc4483021f5436028578d6841025c4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_gamma.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_zeta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_zeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68aa0142ec1875c3390253c5d736cca3484250b3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/extratest_zeta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/runtests.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/runtests.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6be0a4d0d5beef404932d7688dcca7dec467237c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/runtests.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_basic_ops.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_basic_ops.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a002e649b8130d2f2ce6fa45cd9f5ae74ad88037 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_basic_ops.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_bitwise.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_bitwise.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cbe5c03009f34e85524b554636836f09cec3f07 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_bitwise.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_calculus.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_calculus.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d9491a286c44131606f1f7de74cf809065203b2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_calculus.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_compatibility.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_compatibility.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9a06da6fd30711c1a1fde8bbe255489a3c64bd7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_compatibility.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_convert.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_convert.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2e3d8470d3d1717d65f4571d8d0e74f47f90b61 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_convert.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_diff.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_diff.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5443ecd4d071d68ff89eece3cbda331b4ea4a1f8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_diff.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_division.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_division.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8723560ce5165e59c251714be5395c0b74992ae3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_division.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54662fa7c25b12c333720938d54cfeee759919dc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen_symmetric.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen_symmetric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c500935602e670a09142b63ea51342f917ad6ca6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_eigen_symmetric.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_elliptic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_elliptic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2c400e8f5cf48f2a8037d0a2fe85e1188408748 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_elliptic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df8058f7082516f825e0829242b16c298b743bb1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_functions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_gammazeta.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_gammazeta.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e399628e55b0ed9d7413f6ac466e3512d1dcbfb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_gammazeta.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_hp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_hp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..492690a6469e5daa8ae5e20c8638a78e0af0f429 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_hp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_identify.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_identify.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..14056e1d299a24df0469cd5b6bb89864f32e598e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_identify.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_interval.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_interval.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55b815774c471bb87afae9da8b2e893779f0c2b0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_interval.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_levin.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_levin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2c37f3d90427f513ebdeb2081b6624df0561354 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_levin.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_linalg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_linalg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7704f86a9299d1a6ce34ab47b5383abb6dd17758 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_linalg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_matrices.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_matrices.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e07ee6e1f85e54c253d21792d437518eef24e2b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_matrices.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_mpmath.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_mpmath.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39aae06a74da6927dabbef5762cc13c8d1d605ba Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_mpmath.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_ode.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_ode.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9924b5aff025596b9f657a9df7d71d23028dc9d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_ode.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_pickle.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_pickle.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b24f3cdaedc158e060ad814ea74c9abd1aebacb1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_pickle.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_power.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_power.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af29cc441157b7d53040ae03cbf11da65096bd69 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_power.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_quad.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_quad.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e3bc2bd8e97e564d1257043a067641dd0465c4b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_quad.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_rootfinding.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_rootfinding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29d55cc30834853c631f4a0e225f6b781e43d839 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_rootfinding.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_special.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_special.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96546a24c1e1573d70a5edbfda1a333963da07ce Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_special.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_str.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_str.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0adab7d74cfc6aa530d904ac09f15e0b6eab706d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_str.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_summation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_summation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f72f7b71d65b9357dcd4aa072e242e3a9f56221d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_summation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_trig.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_trig.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a568c0c95487809a214867ec07347794fc3f49a1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_trig.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_visualization.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_visualization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a8cca4797676ef05d074b4a1e2d9baf432b2970f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/test_visualization.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/torture.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/torture.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83154b4f5b34d74d5f00fd57482ed50c55626a1f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/__pycache__/torture.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_gamma.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_gamma.py new file mode 100644 index 0000000000000000000000000000000000000000..5a27b61b19aba0abf6bdb8adc16fc1ec7689b67a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_gamma.py @@ -0,0 +1,215 @@ +from mpmath import * +from mpmath.libmp import ifac + +import sys +if "-dps" in sys.argv: + maxdps = int(sys.argv[sys.argv.index("-dps")+1]) +else: + maxdps = 1000 + +raise_ = "-raise" in sys.argv + +errcount = 0 + +def check(name, func, z, y): + global errcount + try: + x = func(z) + except: + errcount += 1 + if raise_: + raise + print() + print(name) + print("EXCEPTION") + import traceback + traceback.print_tb(sys.exc_info()[2]) + print() + return + xre = x.real + xim = x.imag + yre = y.real + yim = y.imag + tol = eps*8 + err = 0 + if abs(xre-yre) > abs(yre)*tol: + err = 1 + print() + print("Error! %s (re = %s, wanted %s, err=%s)" % (name, nstr(xre,10), nstr(yre,10), nstr(abs(xre-yre)))) + errcount += 1 + if raise_: + raise SystemExit + if abs(xim-yim) > abs(yim)*tol: + err = 1 + print() + print("Error! %s (im = %s, wanted %s, err=%s)" % (name, nstr(xim,10), nstr(yim,10), nstr(abs(xim-yim)))) + errcount += 1 + if raise_: + raise SystemExit + if not err: + sys.stdout.write("%s ok; " % name) + +def testcase(case): + z, result = case + print("Testing z =", z) + mp.dps = 1010 + z = eval(z) + mp.dps = maxdps + 50 + if result is None: + gamma_val = gamma(z) + loggamma_val = loggamma(z) + factorial_val = factorial(z) + rgamma_val = rgamma(z) + else: + loggamma_val = eval(result) + gamma_val = exp(loggamma_val) + factorial_val = z * gamma_val + rgamma_val = 1/gamma_val + for dps in [5, 10, 15, 25, 40, 60, 90, 120, 250, 600, 1000, 1800, 3600]: + if dps > maxdps: + break + mp.dps = dps + print("dps = %s" % dps) + check("gamma", gamma, z, gamma_val) + check("rgamma", rgamma, z, rgamma_val) + check("loggamma", loggamma, z, loggamma_val) + check("factorial", factorial, z, factorial_val) + print() + mp.dps = 15 + +testcases = [] + +# Basic values +for n in list(range(1,200)) + list(range(201,2000,17)): + testcases.append(["%s" % n, None]) +for n in range(-200,200): + testcases.append(["%s+0.5" % n, None]) + testcases.append(["%s+0.37" % n, None]) + +testcases += [\ +["(0.1+1j)", None], +["(-0.1+1j)", None], +["(0.1-1j)", None], +["(-0.1-1j)", None], +["10j", None], +["-10j", None], +["100j", None], +["10000j", None], +["-10000000j", None], +["(10**100)*j", None], +["125+(10**100)*j", None], +["-125+(10**100)*j", None], +["(10**10)*(1+j)", None], +["(10**10)*(-1+j)", None], +["(10**100)*(1+j)", None], +["(10**100)*(-1+j)", None], +["(1.5-1j)", None], +["(6+4j)", None], +["(4+1j)", None], +["(3.5+2j)", None], +["(1.5-1j)", None], +["(-6-4j)", None], +["(-2-3j)", None], +["(-2.5-2j)", None], +["(4+1j)", None], +["(3+3j)", None], +["(2-2j)", None], +["1", "0"], +["2", "0"], +["3", "log(2)"], +["4", "log(6)"], +["5", "log(24)"], +["0.5", "log(pi)/2"], +["1.5", "log(sqrt(pi)/2)"], +["2.5", "log(3*sqrt(pi)/4)"], +["mpf('0.37')", None], +["0.25", "log(sqrt(2*sqrt(2*pi**3)/agm(1,sqrt(2))))"], +["-0.4", None], +["mpf('-1.9')", None], +["mpf('12.8')", None], +["mpf('33.7')", None], +["mpf('95.2')", None], +["mpf('160.3')", None], +["mpf('2057.8')", None], +["25", "log(ifac(24))"], +["80", "log(ifac(79))"], +["500", "log(ifac(500-1))"], +["8000", "log(ifac(8000-1))"], +["8000.5", None], +["mpf('8000.1')", None], +["mpf('1.37e10')", None], +["mpf('1.37e10')*(1+j)", None], +["mpf('1.37e10')*(-1+j)", None], +["mpf('1.37e10')*(-1-j)", None], +["mpf('1.37e10')*(-1+j)", None], +["mpf('1.37e100')", None], +["mpf('1.37e100')*(1+j)", None], +["mpf('1.37e100')*(-1+j)", None], +["mpf('1.37e100')*(-1-j)", None], +["mpf('1.37e100')*(-1+j)", None], +["3+4j", +"mpc('" +"-1.7566267846037841105306041816232757851567066070613445016197619371316057169" +"4723618263960834804618463052988607348289672535780644470689771115236512106002" +"5970873471563240537307638968509556191696167970488390423963867031934333890838" +"8009531786948197210025029725361069435208930363494971027388382086721660805397" +"9163230643216054580167976201709951509519218635460317367338612500626714783631" +"7498317478048447525674016344322545858832610325861086336204591943822302971823" +"5161814175530618223688296232894588415495615809337292518431903058265147109853" +"1710568942184987827643886816200452860853873815413367529829631430146227470517" +"6579967222200868632179482214312673161276976117132204633283806161971389519137" +"1243359764435612951384238091232760634271570950240717650166551484551654327989" +"9360285030081716934130446150245110557038117075172576825490035434069388648124" +"6678152254554001586736120762641422590778766100376515737713938521275749049949" +"1284143906816424244705094759339932733567910991920631339597278805393743140853" +"391550313363278558195609260225928','" +"4.74266443803465792819488940755002274088830335171164611359052405215840070271" +"5906813009373171139767051863542508136875688550817670379002790304870822775498" +"2809996675877564504192565392367259119610438951593128982646945990372179860613" +"4294436498090428077839141927485901735557543641049637962003652638924845391650" +"9546290137755550107224907606529385248390667634297183361902055842228798984200" +"9591180450211798341715874477629099687609819466457990642030707080894518168924" +"6805549314043258530272479246115112769957368212585759640878745385160943755234" +"9398036774908108204370323896757543121853650025529763655312360354244898913463" +"7115955702828838923393113618205074162812089732064414530813087483533203244056" +"0546577484241423134079056537777170351934430586103623577814746004431994179990" +"5318522939077992613855205801498201930221975721246498720895122345420698451980" +"0051215797310305885845964334761831751370672996984756815410977750799748813563" +"8784405288158432214886648743541773208808731479748217023665577802702269468013" +"673719173759245720489020315779001')"], +] + +for z in [4, 14, 34, 64]: + testcases.append(["(2+j)*%s/3" % z, None]) + testcases.append(["(-2+j)*%s/3" % z, None]) + testcases.append(["(1+2*j)*%s/3" % z, None]) + testcases.append(["(2-j)*%s/3" % z, None]) + testcases.append(["(20+j)*%s/3" % z, None]) + testcases.append(["(-20+j)*%s/3" % z, None]) + testcases.append(["(1+20*j)*%s/3" % z, None]) + testcases.append(["(20-j)*%s/3" % z, None]) + testcases.append(["(200+j)*%s/3" % z, None]) + testcases.append(["(-200+j)*%s/3" % z, None]) + testcases.append(["(1+200*j)*%s/3" % z, None]) + testcases.append(["(200-j)*%s/3" % z, None]) + +# Poles +for n in [0,1,2,3,4,25,-1,-2,-3,-4,-20,-21,-50,-51,-200,-201,-20000,-20001]: + for t in ['1e-5', '1e-20', '1e-100', '1e-10000']: + testcases.append(["fadd(%s,'%s',exact=True)" % (n, t), None]) + testcases.append(["fsub(%s,'%s',exact=True)" % (n, t), None]) + testcases.append(["fadd(%s,'%sj',exact=True)" % (n, t), None]) + testcases.append(["fsub(%s,'%sj',exact=True)" % (n, t), None]) + +if __name__ == "__main__": + from timeit import default_timer as clock + tot_time = 0.0 + for case in testcases: + t1 = clock() + testcase(case) + t2 = clock() + print("Test time:", t2-t1) + print() + tot_time += (t2-t1) + print("Total time:", tot_time) + print("Errors:", errcount) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_zeta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_zeta.py new file mode 100644 index 0000000000000000000000000000000000000000..582b3d9cbd956b9cdf94309e0e718371fe716101 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/extratest_zeta.py @@ -0,0 +1,30 @@ +from mpmath import zetazero +from timeit import default_timer as clock + +def test_zetazero(): + cases = [\ + (399999999, 156762524.6750591511), + (241389216, 97490234.2276711795), + (526196239, 202950727.691229534), + (542964976, 209039046.578535272), + (1048449112, 388858885.231056486), + (1048449113, 388858885.384337406), + (1048449114, 388858886.002285122), + (1048449115, 388858886.00239369), + (1048449116, 388858886.690745053) + ] + for n, v in cases: + print(n, v) + t1 = clock() + ok = zetazero(n).ae(complex(0.5,v)) + t2 = clock() + print("ok =", ok, ("(time = %s)" % round(t2-t1,3))) + print("Now computing two huge zeros (this may take hours)") + print("Computing zetazero(8637740722917)") + ok = zetazero(8637740722917).ae(complex(0.5,2124447368584.39296466152)) + print("ok =", ok) + ok = zetazero(8637740722918).ae(complex(0.5,2124447368584.39298170604)) + print("ok =", ok) + +if __name__ == "__main__": + test_zetazero() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/runtests.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/runtests.py new file mode 100644 index 0000000000000000000000000000000000000000..70fde272fdc0e05e3d8951edddca380bd36139ab --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/runtests.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python + +""" +python runtests.py -py + Use py.test to run tests (more useful for debugging) + +python runtests.py -coverage + Generate test coverage report. Statistics are written to /tmp + +python runtests.py -profile + Generate profile stats (this is much slower) + +python runtests.py -nogmpy + Run tests without using GMPY even if it exists + +python runtests.py -strict + Enforce extra tests in normalize() + +python runtests.py -local + Insert '../..' at the beginning of sys.path to use local mpmath + +python runtests.py -skip ... + Skip tests from the listed modules + +Additional arguments are used to filter the tests to run. Only files that have +one of the arguments in their name are executed. + +""" + +import sys, os, traceback + +profile = False +if "-profile" in sys.argv: + sys.argv.remove('-profile') + profile = True + +coverage = False +if "-coverage" in sys.argv: + sys.argv.remove('-coverage') + coverage = True + +if "-nogmpy" in sys.argv: + sys.argv.remove('-nogmpy') + os.environ['MPMATH_NOGMPY'] = 'Y' + +if "-strict" in sys.argv: + sys.argv.remove('-strict') + os.environ['MPMATH_STRICT'] = 'Y' + +if "-local" in sys.argv: + sys.argv.remove('-local') + importdir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), + '../..')) +else: + importdir = '' + +# TODO: add a flag for this +testdir = '' + +def testit(importdir='', testdir='', exit_on_fail=False): + """Run all tests in testdir while importing from importdir.""" + if importdir: + sys.path.insert(1, importdir) + if testdir: + sys.path.insert(1, testdir) + import os.path + import mpmath + print("mpmath imported from %s" % os.path.dirname(mpmath.__file__)) + print("mpmath backend: %s" % mpmath.libmp.backend.BACKEND) + print("mpmath mp class: %s" % repr(mpmath.mp)) + print("mpmath version: %s" % mpmath.__version__) + print("Python version: %s" % sys.version) + print("") + if "-py" in sys.argv: + sys.argv.remove('-py') + import py + py.test.cmdline.main() + else: + import glob + from timeit import default_timer as clock + modules = [] + args = sys.argv[1:] + excluded = [] + if '-skip' in args: + excluded = args[args.index('-skip')+1:] + args = args[:args.index('-skip')] + # search for tests in directory of this file if not otherwise specified + if not testdir: + pattern = os.path.dirname(sys.argv[0]) + else: + pattern = testdir + if pattern: + pattern += '/' + pattern += 'test*.py' + # look for tests (respecting specified filter) + for f in glob.glob(pattern): + name = os.path.splitext(os.path.basename(f))[0] + # If run as a script, only run tests given as args, if any are given + if args and __name__ == "__main__": + ok = False + for arg in args: + if arg in name: + ok = True + break + if not ok: + continue + elif name in excluded: + continue + module = __import__(name) + priority = module.__dict__.get('priority', 100) + if priority == 666: + modules = [[priority, name, module]] + break + modules.append([priority, name, module]) + # execute tests + modules.sort() + tstart = clock() + for priority, name, module in modules: + print(name) + for f in sorted(module.__dict__.keys()): + if f.startswith('test_'): + if coverage and ('numpy' in f): + continue + sys.stdout.write(" " + f[5:].ljust(25) + " ") + t1 = clock() + try: + module.__dict__[f]() + except: + etype, evalue, trb = sys.exc_info() + if etype in (KeyboardInterrupt, SystemExit): + raise + print("") + print("TEST FAILED!") + print("") + traceback.print_exc() + if exit_on_fail: + return + t2 = clock() + print("ok " + " " + ("%.7f" % (t2-t1)) + " s") + tend = clock() + print("") + print("finished tests in " + ("%.2f" % (tend-tstart)) + " seconds") + # clean sys.path + if importdir: + sys.path.remove(importdir) + if testdir: + sys.path.remove(testdir) + +if __name__ == '__main__': + if profile: + import cProfile + cProfile.run("testit('%s', '%s')" % (importdir, testdir), sort=1) + elif coverage: + import trace + tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix], + trace=0, count=1) + tracer.run('testit(importdir, testdir)') + r = tracer.results() + r.write_results(show_missing=True, summary=True, coverdir="/tmp") + else: + testit(importdir, testdir) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_basic_ops.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_basic_ops.py new file mode 100644 index 0000000000000000000000000000000000000000..f577c7fa9f9734876b6767f6cc21144df305d82f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_basic_ops.py @@ -0,0 +1,451 @@ +import mpmath +from mpmath import * +from mpmath.libmp import * +import random +import sys + +try: + long = long +except NameError: + long = int + +def test_type_compare(): + assert mpf(2) == mpc(2,0) + assert mpf(0) == mpc(0) + assert mpf(2) != mpc(2, 0.00001) + assert mpf(2) == 2.0 + assert mpf(2) != 3.0 + assert mpf(2) == 2 + assert mpf(2) != '2.0' + assert mpc(2) != '2.0' + +def test_add(): + assert mpf(2.5) + mpf(3) == 5.5 + assert mpf(2.5) + 3 == 5.5 + assert mpf(2.5) + 3.0 == 5.5 + assert 3 + mpf(2.5) == 5.5 + assert 3.0 + mpf(2.5) == 5.5 + assert (3+0j) + mpf(2.5) == 5.5 + assert mpc(2.5) + mpf(3) == 5.5 + assert mpc(2.5) + 3 == 5.5 + assert mpc(2.5) + 3.0 == 5.5 + assert mpc(2.5) + (3+0j) == 5.5 + assert 3 + mpc(2.5) == 5.5 + assert 3.0 + mpc(2.5) == 5.5 + assert (3+0j) + mpc(2.5) == 5.5 + +def test_sub(): + assert mpf(2.5) - mpf(3) == -0.5 + assert mpf(2.5) - 3 == -0.5 + assert mpf(2.5) - 3.0 == -0.5 + assert 3 - mpf(2.5) == 0.5 + assert 3.0 - mpf(2.5) == 0.5 + assert (3+0j) - mpf(2.5) == 0.5 + assert mpc(2.5) - mpf(3) == -0.5 + assert mpc(2.5) - 3 == -0.5 + assert mpc(2.5) - 3.0 == -0.5 + assert mpc(2.5) - (3+0j) == -0.5 + assert 3 - mpc(2.5) == 0.5 + assert 3.0 - mpc(2.5) == 0.5 + assert (3+0j) - mpc(2.5) == 0.5 + +def test_mul(): + assert mpf(2.5) * mpf(3) == 7.5 + assert mpf(2.5) * 3 == 7.5 + assert mpf(2.5) * 3.0 == 7.5 + assert 3 * mpf(2.5) == 7.5 + assert 3.0 * mpf(2.5) == 7.5 + assert (3+0j) * mpf(2.5) == 7.5 + assert mpc(2.5) * mpf(3) == 7.5 + assert mpc(2.5) * 3 == 7.5 + assert mpc(2.5) * 3.0 == 7.5 + assert mpc(2.5) * (3+0j) == 7.5 + assert 3 * mpc(2.5) == 7.5 + assert 3.0 * mpc(2.5) == 7.5 + assert (3+0j) * mpc(2.5) == 7.5 + +def test_div(): + assert mpf(6) / mpf(3) == 2.0 + assert mpf(6) / 3 == 2.0 + assert mpf(6) / 3.0 == 2.0 + assert 6 / mpf(3) == 2.0 + assert 6.0 / mpf(3) == 2.0 + assert (6+0j) / mpf(3.0) == 2.0 + assert mpc(6) / mpf(3) == 2.0 + assert mpc(6) / 3 == 2.0 + assert mpc(6) / 3.0 == 2.0 + assert mpc(6) / (3+0j) == 2.0 + assert 6 / mpc(3) == 2.0 + assert 6.0 / mpc(3) == 2.0 + assert (6+0j) / mpc(3) == 2.0 + +def test_pow(): + assert mpf(6) ** mpf(3) == 216.0 + assert mpf(6) ** 3 == 216.0 + assert mpf(6) ** 3.0 == 216.0 + assert 6 ** mpf(3) == 216.0 + assert 6.0 ** mpf(3) == 216.0 + assert (6+0j) ** mpf(3.0) == 216.0 + assert mpc(6) ** mpf(3) == 216.0 + assert mpc(6) ** 3 == 216.0 + assert mpc(6) ** 3.0 == 216.0 + assert mpc(6) ** (3+0j) == 216.0 + assert 6 ** mpc(3) == 216.0 + assert 6.0 ** mpc(3) == 216.0 + assert (6+0j) ** mpc(3) == 216.0 + +def test_mixed_misc(): + assert 1 + mpf(3) == mpf(3) + 1 == 4 + assert 1 - mpf(3) == -(mpf(3) - 1) == -2 + assert 3 * mpf(2) == mpf(2) * 3 == 6 + assert 6 / mpf(2) == mpf(6) / 2 == 3 + assert 1.0 + mpf(3) == mpf(3) + 1.0 == 4 + assert 1.0 - mpf(3) == -(mpf(3) - 1.0) == -2 + assert 3.0 * mpf(2) == mpf(2) * 3.0 == 6 + assert 6.0 / mpf(2) == mpf(6) / 2.0 == 3 + +def test_add_misc(): + mp.dps = 15 + assert mpf(4) + mpf(-70) == -66 + assert mpf(1) + mpf(1.1)/80 == 1 + 1.1/80 + assert mpf((1, 10000000000)) + mpf(3) == mpf((1, 10000000000)) + assert mpf(3) + mpf((1, 10000000000)) == mpf((1, 10000000000)) + assert mpf((1, -10000000000)) + mpf(3) == mpf(3) + assert mpf(3) + mpf((1, -10000000000)) == mpf(3) + assert mpf(1) + 1e-15 != 1 + assert mpf(1) + 1e-20 == 1 + assert mpf(1.07e-22) + 0 == mpf(1.07e-22) + assert mpf(0) + mpf(1.07e-22) == mpf(1.07e-22) + +def test_complex_misc(): + # many more tests needed + assert 1 + mpc(2) == 3 + assert not mpc(2).ae(2 + 1e-13) + assert mpc(2+1e-15j).ae(2) + +def test_complex_zeros(): + for a in [0,2]: + for b in [0,3]: + for c in [0,4]: + for d in [0,5]: + assert mpc(a,b)*mpc(c,d) == complex(a,b)*complex(c,d) + +def test_hash(): + for i in range(-256, 256): + assert hash(mpf(i)) == hash(i) + assert hash(mpf(0.5)) == hash(0.5) + assert hash(mpc(2,3)) == hash(2+3j) + # Check that this doesn't fail + assert hash(inf) + # Check that overflow doesn't assign equal hashes to large numbers + assert hash(mpf('1e1000')) != hash('1e10000') + assert hash(mpc(100,'1e1000')) != hash(mpc(200,'1e1000')) + from mpmath.rational import mpq + assert hash(mp.mpq(1,3)) + assert hash(mp.mpq(0,1)) == 0 + assert hash(mp.mpq(-1,1)) == hash(-1) + assert hash(mp.mpq(1,1)) == hash(1) + assert hash(mp.mpq(5,1)) == hash(5) + assert hash(mp.mpq(1,2)) == hash(0.5) + if sys.version_info >= (3, 2): + assert hash(mpf(1)*2**2000) == hash(2**2000) + assert hash(mpf(1)/2**2000) == hash(mpq(1,2**2000)) + +# Advanced rounding test +def test_add_rounding(): + mp.dps = 15 + a = from_float(1e-50) + assert mpf_sub(mpf_add(fone, a, 53, round_up), fone, 53, round_up) == from_float(2.2204460492503131e-16) + assert mpf_sub(fone, a, 53, round_up) == fone + assert mpf_sub(fone, mpf_sub(fone, a, 53, round_down), 53, round_down) == from_float(1.1102230246251565e-16) + assert mpf_add(fone, a, 53, round_down) == fone + +def test_almost_equal(): + assert mpf(1.2).ae(mpf(1.20000001), 1e-7) + assert not mpf(1.2).ae(mpf(1.20000001), 1e-9) + assert not mpf(-0.7818314824680298).ae(mpf(-0.774695868667929)) + +def test_arithmetic_functions(): + import operator + ops = [(operator.add, fadd), (operator.sub, fsub), (operator.mul, fmul), + (operator.truediv, fdiv)] + a = mpf(0.27) + b = mpf(1.13) + c = mpc(0.51+2.16j) + d = mpc(1.08-0.99j) + for x in [a,b,c,d]: + for y in [a,b,c,d]: + for op, fop in ops: + if fop is not fdiv: + mp.prec = 200 + z0 = op(x,y) + mp.prec = 60 + z1 = op(x,y) + mp.prec = 53 + z2 = op(x,y) + assert fop(x, y, prec=60) == z1 + assert fop(x, y) == z2 + if fop is not fdiv: + assert fop(x, y, prec=inf) == z0 + assert fop(x, y, dps=inf) == z0 + assert fop(x, y, exact=True) == z0 + assert fneg(fneg(z1, exact=True), prec=inf) == z1 + assert fneg(z1) == -(+z1) + mp.dps = 15 + +def test_exact_integer_arithmetic(): + # XXX: re-fix this so that all operations are tested with all rounding modes + random.seed(0) + for prec in [6, 10, 25, 40, 100, 250, 725]: + for rounding in ['d', 'u', 'f', 'c', 'n']: + mp.dps = prec + M = 10**(prec-2) + M2 = 10**(prec//2-2) + for i in range(10): + a = random.randint(-M, M) + b = random.randint(-M, M) + assert mpf(a, rounding=rounding) == a + assert int(mpf(a, rounding=rounding)) == a + assert int(mpf(str(a), rounding=rounding)) == a + assert mpf(a) + mpf(b) == a + b + assert mpf(a) - mpf(b) == a - b + assert -mpf(a) == -a + a = random.randint(-M2, M2) + b = random.randint(-M2, M2) + assert mpf(a) * mpf(b) == a*b + assert mpf_mul(from_int(a), from_int(b), mp.prec, rounding) == from_int(a*b) + mp.dps = 15 + +def test_odd_int_bug(): + assert to_int(from_int(3), round_nearest) == 3 + +def test_str_1000_digits(): + mp.dps = 1001 + # last digit may be wrong + assert str(mpf(2)**0.5)[-10:-1] == '9518488472'[:9] + assert str(pi)[-10:-1] == '2164201989'[:9] + mp.dps = 15 + +def test_str_10000_digits(): + mp.dps = 10001 + # last digit may be wrong + assert str(mpf(2)**0.5)[-10:-1] == '5873258351'[:9] + assert str(pi)[-10:-1] == '5256375678'[:9] + mp.dps = 15 + +def test_monitor(): + f = lambda x: x**2 + a = [] + b = [] + g = monitor(f, a.append, b.append) + assert g(3) == 9 + assert g(4) == 16 + assert a[0] == ((3,), {}) + assert b[0] == 9 + +def test_nint_distance(): + assert nint_distance(mpf(-3)) == (-3, -inf) + assert nint_distance(mpc(-3)) == (-3, -inf) + assert nint_distance(mpf(-3.1)) == (-3, -3) + assert nint_distance(mpf(-3.01)) == (-3, -6) + assert nint_distance(mpf(-3.001)) == (-3, -9) + assert nint_distance(mpf(-3.0001)) == (-3, -13) + assert nint_distance(mpf(-2.9)) == (-3, -3) + assert nint_distance(mpf(-2.99)) == (-3, -6) + assert nint_distance(mpf(-2.999)) == (-3, -9) + assert nint_distance(mpf(-2.9999)) == (-3, -13) + assert nint_distance(mpc(-3+0.1j)) == (-3, -3) + assert nint_distance(mpc(-3+0.01j)) == (-3, -6) + assert nint_distance(mpc(-3.1+0.1j)) == (-3, -3) + assert nint_distance(mpc(-3.01+0.01j)) == (-3, -6) + assert nint_distance(mpc(-3.001+0.001j)) == (-3, -9) + assert nint_distance(mpf(0)) == (0, -inf) + assert nint_distance(mpf(0.01)) == (0, -6) + assert nint_distance(mpf('1e-100')) == (0, -332) + +def test_floor_ceil_nint_frac(): + mp.dps = 15 + for n in range(-10,10): + assert floor(n) == n + assert floor(n+0.5) == n + assert ceil(n) == n + assert ceil(n+0.5) == n+1 + assert nint(n) == n + # nint rounds to even + if n % 2 == 1: + assert nint(n+0.5) == n+1 + else: + assert nint(n+0.5) == n + assert floor(inf) == inf + assert floor(ninf) == ninf + assert isnan(floor(nan)) + assert ceil(inf) == inf + assert ceil(ninf) == ninf + assert isnan(ceil(nan)) + assert nint(inf) == inf + assert nint(ninf) == ninf + assert isnan(nint(nan)) + assert floor(0.1) == 0 + assert floor(0.9) == 0 + assert floor(-0.1) == -1 + assert floor(-0.9) == -1 + assert floor(10000000000.1) == 10000000000 + assert floor(10000000000.9) == 10000000000 + assert floor(-10000000000.1) == -10000000000-1 + assert floor(-10000000000.9) == -10000000000-1 + assert floor(1e-100) == 0 + assert floor(-1e-100) == -1 + assert floor(1e100) == 1e100 + assert floor(-1e100) == -1e100 + assert ceil(0.1) == 1 + assert ceil(0.9) == 1 + assert ceil(-0.1) == 0 + assert ceil(-0.9) == 0 + assert ceil(10000000000.1) == 10000000000+1 + assert ceil(10000000000.9) == 10000000000+1 + assert ceil(-10000000000.1) == -10000000000 + assert ceil(-10000000000.9) == -10000000000 + assert ceil(1e-100) == 1 + assert ceil(-1e-100) == 0 + assert ceil(1e100) == 1e100 + assert ceil(-1e100) == -1e100 + assert nint(0.1) == 0 + assert nint(0.9) == 1 + assert nint(-0.1) == 0 + assert nint(-0.9) == -1 + assert nint(10000000000.1) == 10000000000 + assert nint(10000000000.9) == 10000000000+1 + assert nint(-10000000000.1) == -10000000000 + assert nint(-10000000000.9) == -10000000000-1 + assert nint(1e-100) == 0 + assert nint(-1e-100) == 0 + assert nint(1e100) == 1e100 + assert nint(-1e100) == -1e100 + assert floor(3.2+4.6j) == 3+4j + assert ceil(3.2+4.6j) == 4+5j + assert nint(3.2+4.6j) == 3+5j + for n in range(-10,10): + assert frac(n) == 0 + assert frac(0.25) == 0.25 + assert frac(1.25) == 0.25 + assert frac(2.25) == 0.25 + assert frac(-0.25) == 0.75 + assert frac(-1.25) == 0.75 + assert frac(-2.25) == 0.75 + assert frac('1e100000000000000') == 0 + u = mpf('1e-100000000000000') + assert frac(u) == u + assert frac(-u) == 1 # rounding! + u = mpf('1e-400') + assert frac(-u, prec=0) == fsub(1, u, exact=True) + assert frac(3.25+4.75j) == 0.25+0.75j + +def test_isnan_etc(): + from mpmath.rational import mpq + assert isnan(nan) == True + assert isnan(3) == False + assert isnan(mpf(3)) == False + assert isnan(inf) == False + assert isnan(mpc(2,nan)) == True + assert isnan(mpc(2,nan)) == True + assert isnan(mpc(nan,nan)) == True + assert isnan(mpc(2,2)) == False + assert isnan(mpc(nan,inf)) == True + assert isnan(mpc(inf,inf)) == False + assert isnan(mpq((3,2))) == False + assert isnan(mpq((0,1))) == False + assert isinf(inf) == True + assert isinf(-inf) == True + assert isinf(3) == False + assert isinf(nan) == False + assert isinf(3+4j) == False + assert isinf(mpc(inf)) == True + assert isinf(mpc(3,inf)) == True + assert isinf(mpc(inf,3)) == True + assert isinf(mpc(inf,inf)) == True + assert isinf(mpc(nan,inf)) == True + assert isinf(mpc(inf,nan)) == True + assert isinf(mpc(nan,nan)) == False + assert isinf(mpq((3,2))) == False + assert isinf(mpq((0,1))) == False + assert isnormal(3) == True + assert isnormal(3.5) == True + assert isnormal(mpf(3.5)) == True + assert isnormal(0) == False + assert isnormal(mpf(0)) == False + assert isnormal(0.0) == False + assert isnormal(inf) == False + assert isnormal(-inf) == False + assert isnormal(nan) == False + assert isnormal(float(inf)) == False + assert isnormal(mpc(0,0)) == False + assert isnormal(mpc(3,0)) == True + assert isnormal(mpc(0,3)) == True + assert isnormal(mpc(3,3)) == True + assert isnormal(mpc(0,nan)) == False + assert isnormal(mpc(0,inf)) == False + assert isnormal(mpc(3,nan)) == False + assert isnormal(mpc(3,inf)) == False + assert isnormal(mpc(3,-inf)) == False + assert isnormal(mpc(nan,0)) == False + assert isnormal(mpc(inf,0)) == False + assert isnormal(mpc(nan,3)) == False + assert isnormal(mpc(inf,3)) == False + assert isnormal(mpc(inf,nan)) == False + assert isnormal(mpc(nan,inf)) == False + assert isnormal(mpc(nan,nan)) == False + assert isnormal(mpc(inf,inf)) == False + assert isnormal(mpq((3,2))) == True + assert isnormal(mpq((0,1))) == False + assert isint(3) == True + assert isint(0) == True + assert isint(long(3)) == True + assert isint(long(0)) == True + assert isint(mpf(3)) == True + assert isint(mpf(0)) == True + assert isint(mpf(-3)) == True + assert isint(mpf(3.2)) == False + assert isint(3.2) == False + assert isint(nan) == False + assert isint(inf) == False + assert isint(-inf) == False + assert isint(mpc(0)) == True + assert isint(mpc(3)) == True + assert isint(mpc(3.2)) == False + assert isint(mpc(3,inf)) == False + assert isint(mpc(inf)) == False + assert isint(mpc(3,2)) == False + assert isint(mpc(0,2)) == False + assert isint(mpc(3,2),gaussian=True) == True + assert isint(mpc(3,0),gaussian=True) == True + assert isint(mpc(0,3),gaussian=True) == True + assert isint(3+4j) == False + assert isint(3+4j, gaussian=True) == True + assert isint(3+0j) == True + assert isint(mpq((3,2))) == False + assert isint(mpq((3,9))) == False + assert isint(mpq((9,3))) == True + assert isint(mpq((0,4))) == True + assert isint(mpq((1,1))) == True + assert isint(mpq((-1,1))) == True + assert mp.isnpint(0) == True + assert mp.isnpint(1) == False + assert mp.isnpint(-1) == True + assert mp.isnpint(-1.1) == False + assert mp.isnpint(-1.0) == True + assert mp.isnpint(mp.mpq(1,2)) == False + assert mp.isnpint(mp.mpq(-1,2)) == False + assert mp.isnpint(mp.mpq(-3,1)) == True + assert mp.isnpint(mp.mpq(0,1)) == True + assert mp.isnpint(mp.mpq(1,1)) == False + assert mp.isnpint(0+0j) == True + assert mp.isnpint(-1+0j) == True + assert mp.isnpint(-1.1+0j) == False + assert mp.isnpint(-1+0.1j) == False + assert mp.isnpint(0+0.1j) == False + + +def test_issue_438(): + assert mpf(finf) == mpf('inf') + assert mpf(fninf) == mpf('-inf') + assert mpf(fnan)._mpf_ == mpf('nan')._mpf_ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_bitwise.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_bitwise.py new file mode 100644 index 0000000000000000000000000000000000000000..4f61b69fc8819cf275abaedd98847c58c3b5924a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_bitwise.py @@ -0,0 +1,188 @@ +""" +Test bit-level integer and mpf operations +""" + +from mpmath import * +from mpmath.libmp import * + +def test_bitcount(): + assert bitcount(0) == 0 + assert bitcount(1) == 1 + assert bitcount(7) == 3 + assert bitcount(8) == 4 + assert bitcount(2**100) == 101 + assert bitcount(2**100-1) == 100 + +def test_trailing(): + assert trailing(0) == 0 + assert trailing(1) == 0 + assert trailing(2) == 1 + assert trailing(7) == 0 + assert trailing(8) == 3 + assert trailing(2**100) == 100 + assert trailing(2**100-1) == 0 + +def test_round_down(): + assert from_man_exp(0, -4, 4, round_down)[:3] == (0, 0, 0) + assert from_man_exp(0xf0, -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(0xf1, -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(0xff, -4, 4, round_down)[:3] == (0, 15, 0) + assert from_man_exp(-0xf0, -4, 4, round_down)[:3] == (1, 15, 0) + assert from_man_exp(-0xf1, -4, 4, round_down)[:3] == (1, 15, 0) + assert from_man_exp(-0xff, -4, 4, round_down)[:3] == (1, 15, 0) + +def test_round_up(): + assert from_man_exp(0, -4, 4, round_up)[:3] == (0, 0, 0) + assert from_man_exp(0xf0, -4, 4, round_up)[:3] == (0, 15, 0) + assert from_man_exp(0xf1, -4, 4, round_up)[:3] == (0, 1, 4) + assert from_man_exp(0xff, -4, 4, round_up)[:3] == (0, 1, 4) + assert from_man_exp(-0xf0, -4, 4, round_up)[:3] == (1, 15, 0) + assert from_man_exp(-0xf1, -4, 4, round_up)[:3] == (1, 1, 4) + assert from_man_exp(-0xff, -4, 4, round_up)[:3] == (1, 1, 4) + +def test_round_floor(): + assert from_man_exp(0, -4, 4, round_floor)[:3] == (0, 0, 0) + assert from_man_exp(0xf0, -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(0xf1, -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(0xff, -4, 4, round_floor)[:3] == (0, 15, 0) + assert from_man_exp(-0xf0, -4, 4, round_floor)[:3] == (1, 15, 0) + assert from_man_exp(-0xf1, -4, 4, round_floor)[:3] == (1, 1, 4) + assert from_man_exp(-0xff, -4, 4, round_floor)[:3] == (1, 1, 4) + +def test_round_ceiling(): + assert from_man_exp(0, -4, 4, round_ceiling)[:3] == (0, 0, 0) + assert from_man_exp(0xf0, -4, 4, round_ceiling)[:3] == (0, 15, 0) + assert from_man_exp(0xf1, -4, 4, round_ceiling)[:3] == (0, 1, 4) + assert from_man_exp(0xff, -4, 4, round_ceiling)[:3] == (0, 1, 4) + assert from_man_exp(-0xf0, -4, 4, round_ceiling)[:3] == (1, 15, 0) + assert from_man_exp(-0xf1, -4, 4, round_ceiling)[:3] == (1, 15, 0) + assert from_man_exp(-0xff, -4, 4, round_ceiling)[:3] == (1, 15, 0) + +def test_round_nearest(): + assert from_man_exp(0, -4, 4, round_nearest)[:3] == (0, 0, 0) + assert from_man_exp(0xf0, -4, 4, round_nearest)[:3] == (0, 15, 0) + assert from_man_exp(0xf7, -4, 4, round_nearest)[:3] == (0, 15, 0) + assert from_man_exp(0xf8, -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1000 -> 10000.0 + assert from_man_exp(0xf9, -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1001 -> 10000.0 + assert from_man_exp(0xe8, -4, 4, round_nearest)[:3] == (0, 7, 1) # 1110.1000 -> 1110.0 + assert from_man_exp(0xe9, -4, 4, round_nearest)[:3] == (0, 15, 0) # 1110.1001 -> 1111.0 + assert from_man_exp(-0xf0, -4, 4, round_nearest)[:3] == (1, 15, 0) + assert from_man_exp(-0xf7, -4, 4, round_nearest)[:3] == (1, 15, 0) + assert from_man_exp(-0xf8, -4, 4, round_nearest)[:3] == (1, 1, 4) + assert from_man_exp(-0xf9, -4, 4, round_nearest)[:3] == (1, 1, 4) + assert from_man_exp(-0xe8, -4, 4, round_nearest)[:3] == (1, 7, 1) + assert from_man_exp(-0xe9, -4, 4, round_nearest)[:3] == (1, 15, 0) + +def test_rounding_bugs(): + # 1 less than power-of-two cases + assert from_man_exp(72057594037927935, -56, 53, round_up) == (0, 1, 0, 1) + assert from_man_exp(73786976294838205979, -65, 53, round_nearest) == (0, 1, 1, 1) + assert from_man_exp(31, 0, 4, round_up) == (0, 1, 5, 1) + assert from_man_exp(-31, 0, 4, round_floor) == (1, 1, 5, 1) + assert from_man_exp(255, 0, 7, round_up) == (0, 1, 8, 1) + assert from_man_exp(-255, 0, 7, round_floor) == (1, 1, 8, 1) + +def test_rounding_issue_200(): + a = from_man_exp(9867,-100) + b = from_man_exp(9867,-200) + c = from_man_exp(-1,0) + z = (1, 1023, -10, 10) + assert mpf_add(a, c, 10, 'd') == z + assert mpf_add(b, c, 10, 'd') == z + assert mpf_add(c, a, 10, 'd') == z + assert mpf_add(c, b, 10, 'd') == z + +def test_perturb(): + a = fone + b = from_float(0.99999999999999989) + c = from_float(1.0000000000000002) + assert mpf_perturb(a, 0, 53, round_nearest) == a + assert mpf_perturb(a, 1, 53, round_nearest) == a + assert mpf_perturb(a, 0, 53, round_up) == c + assert mpf_perturb(a, 0, 53, round_ceiling) == c + assert mpf_perturb(a, 0, 53, round_down) == a + assert mpf_perturb(a, 0, 53, round_floor) == a + assert mpf_perturb(a, 1, 53, round_up) == a + assert mpf_perturb(a, 1, 53, round_ceiling) == a + assert mpf_perturb(a, 1, 53, round_down) == b + assert mpf_perturb(a, 1, 53, round_floor) == b + a = mpf_neg(a) + b = mpf_neg(b) + c = mpf_neg(c) + assert mpf_perturb(a, 0, 53, round_nearest) == a + assert mpf_perturb(a, 1, 53, round_nearest) == a + assert mpf_perturb(a, 0, 53, round_up) == a + assert mpf_perturb(a, 0, 53, round_floor) == a + assert mpf_perturb(a, 0, 53, round_down) == b + assert mpf_perturb(a, 0, 53, round_ceiling) == b + assert mpf_perturb(a, 1, 53, round_up) == c + assert mpf_perturb(a, 1, 53, round_floor) == c + assert mpf_perturb(a, 1, 53, round_down) == a + assert mpf_perturb(a, 1, 53, round_ceiling) == a + +def test_add_exact(): + ff = from_float + assert mpf_add(ff(3.0), ff(2.5)) == ff(5.5) + assert mpf_add(ff(3.0), ff(-2.5)) == ff(0.5) + assert mpf_add(ff(-3.0), ff(2.5)) == ff(-0.5) + assert mpf_add(ff(-3.0), ff(-2.5)) == ff(-5.5) + assert mpf_sub(mpf_add(fone, ff(1e-100)), fone) == ff(1e-100) + assert mpf_sub(mpf_add(ff(1e-100), fone), fone) == ff(1e-100) + assert mpf_sub(mpf_add(fone, ff(-1e-100)), fone) == ff(-1e-100) + assert mpf_sub(mpf_add(ff(-1e-100), fone), fone) == ff(-1e-100) + assert mpf_add(fone, fzero) == fone + assert mpf_add(fzero, fone) == fone + assert mpf_add(fzero, fzero) == fzero + +def test_long_exponent_shifts(): + mp.dps = 15 + # Check for possible bugs due to exponent arithmetic overflow + # in a C implementation + x = mpf(1) + for p in [32, 64]: + a = ldexp(1,2**(p-1)) + b = ldexp(1,2**p) + c = ldexp(1,2**(p+1)) + d = ldexp(1,-2**(p-1)) + e = ldexp(1,-2**p) + f = ldexp(1,-2**(p+1)) + assert (x+a) == a + assert (x+b) == b + assert (x+c) == c + assert (x+d) == x + assert (x+e) == x + assert (x+f) == x + assert (a+x) == a + assert (b+x) == b + assert (c+x) == c + assert (d+x) == x + assert (e+x) == x + assert (f+x) == x + assert (x-a) == -a + assert (x-b) == -b + assert (x-c) == -c + assert (x-d) == x + assert (x-e) == x + assert (x-f) == x + assert (a-x) == a + assert (b-x) == b + assert (c-x) == c + assert (d-x) == -x + assert (e-x) == -x + assert (f-x) == -x + +def test_float_rounding(): + mp.prec = 64 + for x in [mpf(1), mpf(1)+eps, mpf(1)-eps, -mpf(1)+eps, -mpf(1)-eps]: + fa = float(x) + fb = float(fadd(x,0,prec=53,rounding='n')) + assert fa == fb + z = mpc(x,x) + ca = complex(z) + cb = complex(fadd(z,0,prec=53,rounding='n')) + assert ca == cb + for rnd in ['n', 'd', 'u', 'f', 'c']: + fa = to_float(x._mpf_, rnd=rnd) + fb = to_float(fadd(x,0,prec=53,rounding=rnd)._mpf_, rnd=rnd) + assert fa == fb + mp.prec = 53 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_calculus.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_calculus.py new file mode 100644 index 0000000000000000000000000000000000000000..f0a59773d672f0db20bb5072773472a5a3cc1d1f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_calculus.py @@ -0,0 +1,216 @@ +import pytest +from mpmath import * + +def test_approximation(): + mp.dps = 15 + f = lambda x: cos(2-2*x)/x + p, err = chebyfit(f, [2, 4], 8, error=True) + assert err < 1e-5 + for i in range(10): + x = 2 + i/5. + assert abs(polyval(p, x) - f(x)) < err + +def test_limits(): + mp.dps = 15 + assert limit(lambda x: (x-sin(x))/x**3, 0).ae(mpf(1)/6) + assert limit(lambda n: (1+1/n)**n, inf).ae(e) + +def test_polyval(): + assert polyval([], 3) == 0 + assert polyval([0], 3) == 0 + assert polyval([5], 3) == 5 + # 4x^3 - 2x + 5 + p = [4, 0, -2, 5] + assert polyval(p,4) == 253 + assert polyval(p,4,derivative=True) == (253, 190) + +def test_polyroots(): + p = polyroots([1,-4]) + assert p[0].ae(4) + p, q = polyroots([1,2,3]) + assert p.ae(-1 - sqrt(2)*j) + assert q.ae(-1 + sqrt(2)*j) + #this is not a real test, it only tests a specific case + assert polyroots([1]) == [] + pytest.raises(ValueError, lambda: polyroots([0])) + +def test_polyroots_legendre(): + n = 64 + coeffs = [11975573020964041433067793888190275875, 0, + -190100434726484311252477736051902332000, 0, + 1437919688271127330313741595496589239248, 0, + -6897338342113537600691931230430793911840, 0, + 23556405536185284408974715545252277554280, 0, + -60969520211303089058522793175947071316960, 0, + 124284021969194758465450309166353645376880, 0, + -204721258548015217049921875719981284186016, 0, + 277415422258095841688223780704620656114900, 0, + -313237834141273382807123548182995095192800, 0, + 297432255354328395601259515935229287637200, 0, + -239057700565161140389797367947941296605600, 0, + 163356095386193445933028201431093219347160, 0, + -95158890516229191805647495979277603503200, 0, + 47310254620162038075933656063247634556400, 0, + -20071017111583894941305187420771723751200, 0, + 7255051932731034189479516844750603752850, 0, + -2228176940331017311443863996901733412640, 0, + 579006552594977616773047095969088431600, 0, + -126584428502545713788439446082310831200, 0, + 23112325428835593809686977515028663000, 0, + -3491517141958743235617737161547844000, 0, + 431305058712550634988073414073557200, 0, + -42927166660756742088912492757452000, 0, + 3378527005707706553294038781836500, 0, + -205277590220215081719131470288800, 0, + 9330799555464321896324157740400, 0, + -304114948474392713657972548576, 0, + 6695289961520387531608984680, 0, + -91048139350447232095702560, 0, + 659769125727878493447120, 0, + -1905929106580294155360, 0, + 916312070471295267] + + with mp.workdps(3): + with pytest.raises(mp.NoConvergence): + polyroots(coeffs, maxsteps=5, cleanup=True, error=False, + extraprec=n*10) + + roots = polyroots(coeffs, maxsteps=50, cleanup=True, error=False, + extraprec=n*10) + roots = [str(r) for r in roots] + assert roots == \ + ['-0.999', '-0.996', '-0.991', '-0.983', '-0.973', '-0.961', + '-0.946', '-0.93', '-0.911', '-0.889', '-0.866', '-0.841', + '-0.813', '-0.784', '-0.753', '-0.72', '-0.685', '-0.649', + '-0.611', '-0.572', '-0.531', '-0.489', '-0.446', '-0.402', + '-0.357', '-0.311', '-0.265', '-0.217', '-0.17', '-0.121', + '-0.073', '-0.0243', '0.0243', '0.073', '0.121', '0.17', '0.217', + '0.265', '0.311', '0.357', '0.402', '0.446', '0.489', '0.531', + '0.572', '0.611', '0.649', '0.685', '0.72', '0.753', '0.784', + '0.813', '0.841', '0.866', '0.889', '0.911', '0.93', '0.946', + '0.961', '0.973', '0.983', '0.991', '0.996', '0.999'] + +def test_polyroots_legendre_init(): + extra_prec = 100 + coeffs = [11975573020964041433067793888190275875, 0, + -190100434726484311252477736051902332000, 0, + 1437919688271127330313741595496589239248, 0, + -6897338342113537600691931230430793911840, 0, + 23556405536185284408974715545252277554280, 0, + -60969520211303089058522793175947071316960, 0, + 124284021969194758465450309166353645376880, 0, + -204721258548015217049921875719981284186016, 0, + 277415422258095841688223780704620656114900, 0, + -313237834141273382807123548182995095192800, 0, + 297432255354328395601259515935229287637200, 0, + -239057700565161140389797367947941296605600, 0, + 163356095386193445933028201431093219347160, 0, + -95158890516229191805647495979277603503200, 0, + 47310254620162038075933656063247634556400, 0, + -20071017111583894941305187420771723751200, 0, + 7255051932731034189479516844750603752850, 0, + -2228176940331017311443863996901733412640, 0, + 579006552594977616773047095969088431600, 0, + -126584428502545713788439446082310831200, 0, + 23112325428835593809686977515028663000, 0, + -3491517141958743235617737161547844000, 0, + 431305058712550634988073414073557200, 0, + -42927166660756742088912492757452000, 0, + 3378527005707706553294038781836500, 0, + -205277590220215081719131470288800, 0, + 9330799555464321896324157740400, 0, + -304114948474392713657972548576, 0, + 6695289961520387531608984680, 0, + -91048139350447232095702560, 0, + 659769125727878493447120, 0, + -1905929106580294155360, 0, + 916312070471295267] + + roots_init = matrix(['-0.999', '-0.996', '-0.991', '-0.983', '-0.973', + '-0.961', '-0.946', '-0.93', '-0.911', '-0.889', + '-0.866', '-0.841', '-0.813', '-0.784', '-0.753', + '-0.72', '-0.685', '-0.649', '-0.611', '-0.572', + '-0.531', '-0.489', '-0.446', '-0.402', '-0.357', + '-0.311', '-0.265', '-0.217', '-0.17', '-0.121', + '-0.073', '-0.0243', '0.0243', '0.073', '0.121', + '0.17', '0.217', '0.265', ' 0.311', '0.357', + '0.402', '0.446', '0.489', '0.531', '0.572', + '0.611', '0.649', '0.685', '0.72', '0.753', + '0.784', '0.813', '0.841', '0.866', '0.889', + '0.911', '0.93', '0.946', '0.961', '0.973', + '0.983', '0.991', '0.996', '0.999', '1.0']) + with mp.workdps(2*mp.dps): + roots_exact = polyroots(coeffs, maxsteps=50, cleanup=True, error=False, + extraprec=2*extra_prec) + with pytest.raises(mp.NoConvergence): + polyroots(coeffs, maxsteps=5, cleanup=True, error=False, + extraprec=extra_prec) + roots,err = polyroots(coeffs, maxsteps=5, cleanup=True, error=True, + extraprec=extra_prec,roots_init=roots_init) + assert max(matrix(roots_exact)-matrix(roots).apply(abs)) < err + roots1,err1 = polyroots(coeffs, maxsteps=25, cleanup=True, error=True, + extraprec=extra_prec,roots_init=roots_init[:60]) + assert max(matrix(roots_exact)-matrix(roots1).apply(abs)) < err1 + +def test_pade(): + one = mpf(1) + mp.dps = 20 + N = 10 + a = [one] + k = 1 + for i in range(1, N+1): + k *= i + a.append(one/k) + p, q = pade(a, N//2, N//2) + for x in arange(0, 1, 0.1): + r = polyval(p[::-1], x)/polyval(q[::-1], x) + assert(r.ae(exp(x), 1.0e-10)) + mp.dps = 15 + +def test_fourier(): + mp.dps = 15 + c, s = fourier(lambda x: x+1, [-1, 2], 2) + #plot([lambda x: x+1, lambda x: fourierval((c, s), [-1, 2], x)], [-1, 2]) + assert c[0].ae(1.5) + assert c[1].ae(-3*sqrt(3)/(2*pi)) + assert c[2].ae(3*sqrt(3)/(4*pi)) + assert s[0] == 0 + assert s[1].ae(3/(2*pi)) + assert s[2].ae(3/(4*pi)) + assert fourierval((c, s), [-1, 2], 1).ae(1.9134966715663442) + +def test_differint(): + mp.dps = 15 + assert differint(lambda t: t, 2, -0.5).ae(8*sqrt(2/pi)/3) + +def test_invlap(): + mp.dps = 15 + t = 0.01 + fp = lambda p: 1/(p+1)**2 + ft = lambda t: t*exp(-t) + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + t = 1.0 + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + + t = 0.01 + fp = lambda p: log(p)/p + ft = lambda t: -euler-log(t) + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) + t = 1.0 + ftt = ft(t) + assert invertlaplace(fp,t,method='talbot').ae(ftt) + assert invertlaplace(fp,t,method='stehfest').ae(ftt) + assert invertlaplace(fp,t,method='dehoog').ae(ftt) + assert invertlaplace(fp,t,method='cohen').ae(ftt) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_compatibility.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_compatibility.py new file mode 100644 index 0000000000000000000000000000000000000000..f26d6044b521306b6d1eaeadc5c7839be226dc54 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_compatibility.py @@ -0,0 +1,77 @@ +from mpmath import * +from random import seed, randint, random +import math + +# Test compatibility with Python floats, which are +# IEEE doubles (53-bit) + +N = 5000 +seed(1) + +# Choosing exponents between roughly -140, 140 ensures that +# the Python floats don't overflow or underflow +xs = [(random()-1) * 10**randint(-140, 140) for x in range(N)] +ys = [(random()-1) * 10**randint(-140, 140) for x in range(N)] + +# include some equal values +ys[int(N*0.8):] = xs[int(N*0.8):] + +# Detect whether Python is compiled to use 80-bit floating-point +# instructions, in which case the double compatibility test breaks +uses_x87 = -4.1974624032366689e+117 / -8.4657370748010221e-47 \ + == 4.9581771393902231e+163 + +def test_double_compatibility(): + mp.prec = 53 + for x, y in zip(xs, ys): + mpx = mpf(x) + mpy = mpf(y) + assert mpf(x) == x + assert (mpx < mpy) == (x < y) + assert (mpx > mpy) == (x > y) + assert (mpx == mpy) == (x == y) + assert (mpx != mpy) == (x != y) + assert (mpx <= mpy) == (x <= y) + assert (mpx >= mpy) == (x >= y) + assert mpx == mpx + if uses_x87: + mp.prec = 64 + a = mpx + mpy + b = mpx * mpy + c = mpx / mpy + d = mpx % mpy + mp.prec = 53 + assert +a == x + y + assert +b == x * y + assert +c == x / y + assert +d == x % y + else: + assert mpx + mpy == x + y + assert mpx * mpy == x * y + assert mpx / mpy == x / y + assert mpx % mpy == x % y + assert abs(mpx) == abs(x) + assert mpf(repr(x)) == x + assert ceil(mpx) == math.ceil(x) + assert floor(mpx) == math.floor(x) + +def test_sqrt(): + # this fails quite often. it appers to be float + # that rounds the wrong way, not mpf + fail = 0 + mp.prec = 53 + for x in xs: + x = abs(x) + mp.prec = 100 + mp_high = mpf(x)**0.5 + mp.prec = 53 + mp_low = mpf(x)**0.5 + fp = x**0.5 + assert abs(mp_low-mp_high) <= abs(fp-mp_high) + fail += mp_low != fp + assert fail < N/10 + +def test_bugs(): + # particular bugs + assert mpf(4.4408920985006262E-16) < mpf(1.7763568394002505E-15) + assert mpf(-4.4408920985006262E-16) > mpf(-1.7763568394002505E-15) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_convert.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_convert.py new file mode 100644 index 0000000000000000000000000000000000000000..cb1db5b55c89e980e08fc3fa43cc9715ad68cac9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_convert.py @@ -0,0 +1,233 @@ +import random +from mpmath import * +from mpmath.libmp import * + + +def test_basic_string(): + """ + Test basic string conversion + """ + mp.dps = 15 + assert mpf('3') == mpf('3.0') == mpf('0003.') == mpf('0.03e2') == mpf(3.0) + assert mpf('30') == mpf('30.0') == mpf('00030.') == mpf(30.0) + for i in range(10): + for j in range(10): + assert mpf('%ie%i' % (i,j)) == i * 10**j + assert str(mpf('25000.0')) == '25000.0' + assert str(mpf('2500.0')) == '2500.0' + assert str(mpf('250.0')) == '250.0' + assert str(mpf('25.0')) == '25.0' + assert str(mpf('2.5')) == '2.5' + assert str(mpf('0.25')) == '0.25' + assert str(mpf('0.025')) == '0.025' + assert str(mpf('0.0025')) == '0.0025' + assert str(mpf('0.00025')) == '0.00025' + assert str(mpf('0.000025')) == '2.5e-5' + assert str(mpf(0)) == '0.0' + assert str(mpf('2.5e1000000000000000000000')) == '2.5e+1000000000000000000000' + assert str(mpf('2.6e-1000000000000000000000')) == '2.6e-1000000000000000000000' + assert str(mpf(1.23402834e-15)) == '1.23402834e-15' + assert str(mpf(-1.23402834e-15)) == '-1.23402834e-15' + assert str(mpf(-1.2344e-15)) == '-1.2344e-15' + assert repr(mpf(-1.2344e-15)) == "mpf('-1.2343999999999999e-15')" + assert str(mpf("2163048125L")) == '2163048125.0' + assert str(mpf("-2163048125l")) == '-2163048125.0' + assert str(mpf("-2163048125L/1088391168")) == '-1.98738118113799' + assert str(mpf("2163048125/1088391168l")) == '1.98738118113799' + +def test_pretty(): + mp.pretty = True + assert repr(mpf(2.5)) == '2.5' + assert repr(mpc(2.5,3.5)) == '(2.5 + 3.5j)' + mp.pretty = False + iv.pretty = True + assert repr(mpi(2.5,3.5)) == '[2.5, 3.5]' + iv.pretty = False + +def test_str_whitespace(): + assert mpf('1.26 ') == 1.26 + +def test_unicode(): + mp.dps = 15 + try: + unicode = unicode + except NameError: + unicode = str + assert mpf(unicode('2.76')) == 2.76 + assert mpf(unicode('inf')) == inf + +def test_str_format(): + assert to_str(from_float(0.1),15,strip_zeros=False) == '0.100000000000000' + assert to_str(from_float(0.0),15,show_zero_exponent=True) == '0.0e+0' + assert to_str(from_float(0.0),0,show_zero_exponent=True) == '.0e+0' + assert to_str(from_float(0.0),0,show_zero_exponent=False) == '.0' + assert to_str(from_float(0.0),1,show_zero_exponent=True) == '0.0e+0' + assert to_str(from_float(0.0),1,show_zero_exponent=False) == '0.0' + assert to_str(from_float(1.23),3,show_zero_exponent=True) == '1.23e+0' + assert to_str(from_float(1.23456789000000e-2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e-2' + assert to_str(from_float(1.23456789000000e+2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e+2' + assert to_str(from_float(2.1287e14), 15, max_fixed=1000) == '212870000000000.0' + assert to_str(from_float(2.1287e15), 15, max_fixed=1000) == '2128700000000000.0' + assert to_str(from_float(2.1287e16), 15, max_fixed=1000) == '21287000000000000.0' + assert to_str(from_float(2.1287e30), 15, max_fixed=1000) == '2128700000000000000000000000000.0' + +def test_tight_string_conversion(): + mp.dps = 15 + # In an old version, '0.5' wasn't recognized as representing + # an exact binary number and was erroneously rounded up or down + assert from_str('0.5', 10, round_floor) == fhalf + assert from_str('0.5', 10, round_ceiling) == fhalf + +def test_eval_repr_invariant(): + """Test that eval(repr(x)) == x""" + random.seed(123) + for dps in [10, 15, 20, 50, 100]: + mp.dps = dps + for i in range(1000): + a = mpf(random.random())**0.5 * 10**random.randint(-100, 100) + assert eval(repr(a)) == a + mp.dps = 15 + +def test_str_bugs(): + mp.dps = 15 + # Decimal rounding used to give the wrong exponent in some cases + assert str(mpf('1e600')) == '1.0e+600' + assert str(mpf('1e10000')) == '1.0e+10000' + +def test_str_prec0(): + assert to_str(from_float(1.234), 0) == '.0e+0' + assert to_str(from_float(1e-15), 0) == '.0e-15' + assert to_str(from_float(1e+15), 0) == '.0e+15' + assert to_str(from_float(-1e-15), 0) == '-.0e-15' + assert to_str(from_float(-1e+15), 0) == '-.0e+15' + +def test_convert_rational(): + mp.dps = 15 + assert from_rational(30, 5, 53, round_nearest) == (0, 3, 1, 2) + assert from_rational(-7, 4, 53, round_nearest) == (1, 7, -2, 3) + assert to_rational((0, 1, -1, 1)) == (1, 2) + +def test_custom_class(): + class mympf: + @property + def _mpf_(self): + return mpf(3.5)._mpf_ + class mympc: + @property + def _mpc_(self): + return mpf(3.5)._mpf_, mpf(2.5)._mpf_ + assert mpf(2) + mympf() == 5.5 + assert mympf() + mpf(2) == 5.5 + assert mpf(mympf()) == 3.5 + assert mympc() + mpc(2) == mpc(5.5, 2.5) + assert mpc(2) + mympc() == mpc(5.5, 2.5) + assert mpc(mympc()) == (3.5+2.5j) + +def test_conversion_methods(): + class SomethingRandom: + pass + class SomethingReal: + def _mpmath_(self, prec, rounding): + return mp.make_mpf(from_str('1.3', prec, rounding)) + class SomethingComplex: + def _mpmath_(self, prec, rounding): + return mp.make_mpc((from_str('1.3', prec, rounding), \ + from_str('1.7', prec, rounding))) + x = mpf(3) + z = mpc(3) + a = SomethingRandom() + y = SomethingReal() + w = SomethingComplex() + for d in [15, 45]: + mp.dps = d + assert (x+y).ae(mpf('4.3')) + assert (y+x).ae(mpf('4.3')) + assert (x+w).ae(mpc('4.3', '1.7')) + assert (w+x).ae(mpc('4.3', '1.7')) + assert (z+y).ae(mpc('4.3')) + assert (y+z).ae(mpc('4.3')) + assert (z+w).ae(mpc('4.3', '1.7')) + assert (w+z).ae(mpc('4.3', '1.7')) + x-y; y-x; x-w; w-x; z-y; y-z; z-w; w-z + x*y; y*x; x*w; w*x; z*y; y*z; z*w; w*z + x/y; y/x; x/w; w/x; z/y; y/z; z/w; w/z + x**y; y**x; x**w; w**x; z**y; y**z; z**w; w**z + x==y; y==x; x==w; w==x; z==y; y==z; z==w; w==z + mp.dps = 15 + assert x.__add__(a) is NotImplemented + assert x.__radd__(a) is NotImplemented + assert x.__lt__(a) is NotImplemented + assert x.__gt__(a) is NotImplemented + assert x.__le__(a) is NotImplemented + assert x.__ge__(a) is NotImplemented + assert x.__eq__(a) is NotImplemented + assert x.__ne__(a) is NotImplemented + # implementation detail + if hasattr(x, "__cmp__"): + assert x.__cmp__(a) is NotImplemented + assert x.__sub__(a) is NotImplemented + assert x.__rsub__(a) is NotImplemented + assert x.__mul__(a) is NotImplemented + assert x.__rmul__(a) is NotImplemented + assert x.__div__(a) is NotImplemented + assert x.__rdiv__(a) is NotImplemented + assert x.__mod__(a) is NotImplemented + assert x.__rmod__(a) is NotImplemented + assert x.__pow__(a) is NotImplemented + assert x.__rpow__(a) is NotImplemented + assert z.__add__(a) is NotImplemented + assert z.__radd__(a) is NotImplemented + assert z.__eq__(a) is NotImplemented + assert z.__ne__(a) is NotImplemented + assert z.__sub__(a) is NotImplemented + assert z.__rsub__(a) is NotImplemented + assert z.__mul__(a) is NotImplemented + assert z.__rmul__(a) is NotImplemented + assert z.__div__(a) is NotImplemented + assert z.__rdiv__(a) is NotImplemented + assert z.__pow__(a) is NotImplemented + assert z.__rpow__(a) is NotImplemented + +def test_mpmathify(): + assert mpmathify('1/2') == 0.5 + assert mpmathify('(1.0+1.0j)') == mpc(1, 1) + assert mpmathify('(1.2e-10 - 3.4e5j)') == mpc('1.2e-10', '-3.4e5') + assert mpmathify('1j') == mpc(1j) + +def test_issue548(): + try: + # This expression is invalid, but may trigger the ReDOS vulnerability + # in the regular expression for parsing complex numbers. + mpmathify('(' + '1' * 5000 + '!j') + except: + return + # The expression is invalid and should raise an exception. + assert False + +def test_compatibility(): + try: + import numpy as np + from fractions import Fraction + from decimal import Decimal + import decimal + except ImportError: + return + # numpy types + for nptype in np.core.numerictypes.typeDict.values(): + if issubclass(nptype, np.complexfloating): + x = nptype(complex(0.5, -0.5)) + elif issubclass(nptype, np.floating): + x = nptype(0.5) + elif issubclass(nptype, np.integer): + x = nptype(2) + # Handle the weird types + try: diff = np.abs(type(np.sqrt(x))(sqrt(x)) - np.sqrt(x)) + except: continue + assert diff < 2.0**-53 + #Fraction and Decimal + oldprec = mp.prec + mp.prec = 1000 + decimal.getcontext().prec = mp.dps + assert sqrt(Fraction(2, 3)).ae(sqrt(mpf('2/3'))) + assert sqrt(Decimal(2)/Decimal(3)).ae(sqrt(mpf('2/3'))) + mp.prec = oldprec diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_diff.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..f5711609da38862eb4fd62c88d35f1704c9425a4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_diff.py @@ -0,0 +1,61 @@ +from mpmath import * + +def test_diff(): + mp.dps = 15 + assert diff(log, 2.0, n=0).ae(log(2)) + assert diff(cos, 1.0).ae(-sin(1)) + assert diff(abs, 0.0) == 0 + assert diff(abs, 0.0, direction=1) == 1 + assert diff(abs, 0.0, direction=-1) == -1 + assert diff(exp, 1.0).ae(e) + assert diff(exp, 1.0, n=5).ae(e) + assert diff(exp, 2.0, n=5, direction=3*j).ae(e**2) + assert diff(lambda x: x**2, 3.0, method='quad').ae(6) + assert diff(lambda x: 3+x**5, 3.0, n=2, method='quad').ae(540) + assert diff(lambda x: 3+x**5, 3.0, n=2, method='step').ae(540) + assert diffun(sin)(2).ae(cos(2)) + assert diffun(sin, n=2)(2).ae(-sin(2)) + +def test_diffs(): + mp.dps = 15 + assert [chop(d) for d in diffs(sin, 0, 1)] == [0, 1] + assert [chop(d) for d in diffs(sin, 0, 1, method='quad')] == [0, 1] + assert [chop(d) for d in diffs(sin, 0, 2)] == [0, 1, 0] + assert [chop(d) for d in diffs(sin, 0, 2, method='quad')] == [0, 1, 0] + +def test_taylor(): + mp.dps = 15 + # Easy to test since the coefficients are exact in floating-point + assert taylor(sqrt, 1, 4) == [1, 0.5, -0.125, 0.0625, -0.0390625] + +def test_diff_partial(): + mp.dps = 15 + x,y,z = xyz = 2,3,7 + f = lambda x,y,z: 3*x**2 * (y+2)**3 * z**5 + assert diff(f, xyz, (0,0,0)).ae(25210500) + assert diff(f, xyz, (0,0,1)).ae(18007500) + assert diff(f, xyz, (0,0,2)).ae(10290000) + assert diff(f, xyz, (0,1,0)).ae(15126300) + assert diff(f, xyz, (0,1,1)).ae(10804500) + assert diff(f, xyz, (0,1,2)).ae(6174000) + assert diff(f, xyz, (0,2,0)).ae(6050520) + assert diff(f, xyz, (0,2,1)).ae(4321800) + assert diff(f, xyz, (0,2,2)).ae(2469600) + assert diff(f, xyz, (1,0,0)).ae(25210500) + assert diff(f, xyz, (1,0,1)).ae(18007500) + assert diff(f, xyz, (1,0,2)).ae(10290000) + assert diff(f, xyz, (1,1,0)).ae(15126300) + assert diff(f, xyz, (1,1,1)).ae(10804500) + assert diff(f, xyz, (1,1,2)).ae(6174000) + assert diff(f, xyz, (1,2,0)).ae(6050520) + assert diff(f, xyz, (1,2,1)).ae(4321800) + assert diff(f, xyz, (1,2,2)).ae(2469600) + assert diff(f, xyz, (2,0,0)).ae(12605250) + assert diff(f, xyz, (2,0,1)).ae(9003750) + assert diff(f, xyz, (2,0,2)).ae(5145000) + assert diff(f, xyz, (2,1,0)).ae(7563150) + assert diff(f, xyz, (2,1,1)).ae(5402250) + assert diff(f, xyz, (2,1,2)).ae(3087000) + assert diff(f, xyz, (2,2,0)).ae(3025260) + assert diff(f, xyz, (2,2,1)).ae(2160900) + assert diff(f, xyz, (2,2,2)).ae(1234800) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_division.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_division.py new file mode 100644 index 0000000000000000000000000000000000000000..c704cadeb953793ac0a887aa09c4278cf68a2824 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_division.py @@ -0,0 +1,143 @@ +from mpmath.libmp import * +from mpmath import mpf, mp + +from random import randint, choice, seed + +all_modes = [round_floor, round_ceiling, round_down, round_up, round_nearest] + +fb = from_bstr +fi = from_int +ff = from_float + + +def test_div_1_3(): + a = fi(1) + b = fi(3) + c = fi(-1) + + # floor rounds down, ceiling rounds up + assert mpf_div(a, b, 7, round_floor) == fb('0.01010101') + assert mpf_div(a, b, 7, round_ceiling) == fb('0.01010110') + assert mpf_div(a, b, 7, round_down) == fb('0.01010101') + assert mpf_div(a, b, 7, round_up) == fb('0.01010110') + assert mpf_div(a, b, 7, round_nearest) == fb('0.01010101') + + # floor rounds up, ceiling rounds down + assert mpf_div(c, b, 7, round_floor) == fb('-0.01010110') + assert mpf_div(c, b, 7, round_ceiling) == fb('-0.01010101') + assert mpf_div(c, b, 7, round_down) == fb('-0.01010101') + assert mpf_div(c, b, 7, round_up) == fb('-0.01010110') + assert mpf_div(c, b, 7, round_nearest) == fb('-0.01010101') + +def test_mpf_divi_1_3(): + a = 1 + b = fi(3) + c = -1 + assert mpf_rdiv_int(a, b, 7, round_floor) == fb('0.01010101') + assert mpf_rdiv_int(a, b, 7, round_ceiling) == fb('0.01010110') + assert mpf_rdiv_int(a, b, 7, round_down) == fb('0.01010101') + assert mpf_rdiv_int(a, b, 7, round_up) == fb('0.01010110') + assert mpf_rdiv_int(a, b, 7, round_nearest) == fb('0.01010101') + assert mpf_rdiv_int(c, b, 7, round_floor) == fb('-0.01010110') + assert mpf_rdiv_int(c, b, 7, round_ceiling) == fb('-0.01010101') + assert mpf_rdiv_int(c, b, 7, round_down) == fb('-0.01010101') + assert mpf_rdiv_int(c, b, 7, round_up) == fb('-0.01010110') + assert mpf_rdiv_int(c, b, 7, round_nearest) == fb('-0.01010101') + + +def test_div_300(): + + q = fi(1000000) + a = fi(300499999) # a/q is a little less than a half-integer + b = fi(300500000) # b/q exactly a half-integer + c = fi(300500001) # c/q is a little more than a half-integer + + # Check nearest integer rounding (prec=9 as 2**8 < 300 < 2**9) + + assert mpf_div(a, q, 9, round_down) == fi(300) + assert mpf_div(b, q, 9, round_down) == fi(300) + assert mpf_div(c, q, 9, round_down) == fi(300) + assert mpf_div(a, q, 9, round_up) == fi(301) + assert mpf_div(b, q, 9, round_up) == fi(301) + assert mpf_div(c, q, 9, round_up) == fi(301) + + # Nearest even integer is down + assert mpf_div(a, q, 9, round_nearest) == fi(300) + assert mpf_div(b, q, 9, round_nearest) == fi(300) + assert mpf_div(c, q, 9, round_nearest) == fi(301) + + # Nearest even integer is up + a = fi(301499999) + b = fi(301500000) + c = fi(301500001) + assert mpf_div(a, q, 9, round_nearest) == fi(301) + assert mpf_div(b, q, 9, round_nearest) == fi(302) + assert mpf_div(c, q, 9, round_nearest) == fi(302) + + +def test_tight_integer_division(): + # Test that integer division at tightest possible precision is exact + N = 100 + seed(1) + for i in range(N): + a = choice([1, -1]) * randint(1, 1< 1: + print("original matrix (hessenberg):\n", A) + + n = A.rows + + Q, H = mp.hessenberg(A) + + if verbose > 1: + print("Q:\n",Q) + print("H:\n",H) + + B = Q * H * Q.transpose_conj() + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for x in xrange(n): + for y in xrange(n): + err0 += abs(A[y,x] - B[y,x]) + err0 /= n * n + + err1 = 0 + for x in xrange(n): + for y in xrange(x + 2, n): + err1 += abs(H[y,x]) + + if verbose > 0: + print("difference (H):", err0, err1) + + if verbose > 1: + print("B:\n", B) + + assert err0 < eps + assert err1 == 0 + + +def run_schur(A, verbose = 0): + if verbose > 1: + print("original matrix (schur):\n", A) + + n = A.rows + + Q, R = mp.schur(A) + + if verbose > 1: + print("Q:\n", Q) + print("R:\n", R) + + B = Q * R * Q.transpose_conj() + C = Q * Q.transpose_conj() + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for x in xrange(n): + for y in xrange(n): + err0 += abs(A[y,x] - B[y,x]) + err0 /= n * n + + err1 = 0 + for x in xrange(n): + for y in xrange(n): + if x == y: + C[y,x] -= 1 + err1 += abs(C[y,x]) + err1 /= n * n + + err2 = 0 + for x in xrange(n): + for y in xrange(x + 1, n): + err2 += abs(R[y,x]) + + if verbose > 0: + print("difference (S):", err0, err1, err2) + + if verbose > 1: + print("B:\n", B) + + assert err0 < eps + assert err1 < eps + assert err2 == 0 + +def run_eig(A, verbose = 0): + if verbose > 1: + print("original matrix (eig):\n", A) + + n = A.rows + + E, EL, ER = mp.eig(A, left = True, right = True) + + if verbose > 1: + print("E:\n", E) + print("EL:\n", EL) + print("ER:\n", ER) + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + err0 = 0 + for i in xrange(n): + B = A * ER[:,i] - E[i] * ER[:,i] + err0 = max(err0, mp.mnorm(B)) + + B = EL[i,:] * A - EL[i,:] * E[i] + err0 = max(err0, mp.mnorm(B)) + + err0 /= n * n + + if verbose > 0: + print("difference (E):", err0) + + assert err0 < eps + +##################### + +def test_eig_dyn(): + v = 0 + for i in xrange(5): + n = 1 + int(mp.rand() * 5) + if mp.rand() > 0.5: + # real + A = 2 * mp.randmatrix(n, n) - 1 + if mp.rand() > 0.5: + A *= 10 + for x in xrange(n): + for y in xrange(n): + A[x,y] = int(A[x,y]) + else: + A = (2 * mp.randmatrix(n, n) - 1) + 1j * (2 * mp.randmatrix(n, n) - 1) + if mp.rand() > 0.5: + A *= 10 + for x in xrange(n): + for y in xrange(n): + A[x,y] = int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y])) + + run_hessenberg(A, verbose = v) + run_schur(A, verbose = v) + run_eig(A, verbose = v) + +def test_eig(): + v = 0 + AS = [] + + A = mp.matrix([[2, 1, 0], # jordan block of size 3 + [0, 2, 1], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A = mp.matrix([[2, 0, 0], # jordan block of size 2 + [0, 2, 1], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A = mp.matrix([[2, 0, 1], # jordan block of size 2 + [0, 2, 0], + [0, 0, 2]]) + AS.append(A) + AS.append(A.transpose()) + + A= mp.matrix([[0, 0, 1], # cyclic + [1, 0, 0], + [0, 1, 0]]) + AS.append(A) + AS.append(A.transpose()) + + for A in AS: + run_hessenberg(A, verbose = v) + run_schur(A, verbose = v) + run_eig(A, verbose = v) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_eigen_symmetric.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_eigen_symmetric.py new file mode 100644 index 0000000000000000000000000000000000000000..aab3d8ea3142aada6e14ad6d3ea25a7e8293554d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_eigen_symmetric.py @@ -0,0 +1,357 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from mpmath import mp +from mpmath import libmp + +xrange = libmp.backend.xrange + +def run_eigsy(A, verbose = False): + if verbose: + print("original matrix:\n", str(A)) + + D, Q = mp.eigsy(A) + B = Q * mp.diag(D) * Q.transpose() + C = A - B + E = Q * Q.transpose() - mp.eye(A.rows) + + if verbose: + print("eigenvalues:\n", D) + print("eigenvectors:\n", Q) + + NC = mp.mnorm(C) + NE = mp.mnorm(E) + + if verbose: + print("difference:", NC, "\n", C, "\n") + print("difference:", NE, "\n", E, "\n") + + eps = mp.exp( 0.8 * mp.log(mp.eps)) + + assert NC < eps + assert NE < eps + + return NC + +def run_eighe(A, verbose = False): + if verbose: + print("original matrix:\n", str(A)) + + D, Q = mp.eighe(A) + B = Q * mp.diag(D) * Q.transpose_conj() + C = A - B + E = Q * Q.transpose_conj() - mp.eye(A.rows) + + if verbose: + print("eigenvalues:\n", D) + print("eigenvectors:\n", Q) + + NC = mp.mnorm(C) + NE = mp.mnorm(E) + + if verbose: + print("difference:", NC, "\n", C, "\n") + print("difference:", NE, "\n", E, "\n") + + eps = mp.exp( 0.8 * mp.log(mp.eps)) + + assert NC < eps + assert NE < eps + + return NC + +def run_svd_r(A, full_matrices = False, verbose = True): + + m, n = A.rows, A.cols + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + if verbose: + print("original matrix:\n", str(A)) + print("full", full_matrices) + + U, S0, V = mp.svd_r(A, full_matrices = full_matrices) + + S = mp.zeros(U.cols, V.rows) + for j in xrange(min(m, n)): + S[j,j] = S0[j] + + if verbose: + print("U:\n", str(U)) + print("S:\n", str(S0)) + print("V:\n", str(V)) + + C = U * S * V - A + err = mp.mnorm(C) + if verbose: + print("C\n", str(C), "\n", err) + assert err < eps + + D = V * V.transpose() - mp.eye(V.rows) + err = mp.mnorm(D) + if verbose: + print("D:\n", str(D), "\n", err) + assert err < eps + + E = U.transpose() * U - mp.eye(U.cols) + err = mp.mnorm(E) + if verbose: + print("E:\n", str(E), "\n", err) + assert err < eps + +def run_svd_c(A, full_matrices = False, verbose = True): + + m, n = A.rows, A.cols + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + if verbose: + print("original matrix:\n", str(A)) + print("full", full_matrices) + + U, S0, V = mp.svd_c(A, full_matrices = full_matrices) + + S = mp.zeros(U.cols, V.rows) + for j in xrange(min(m, n)): + S[j,j] = S0[j] + + if verbose: + print("U:\n", str(U)) + print("S:\n", str(S0)) + print("V:\n", str(V)) + + C = U * S * V - A + err = mp.mnorm(C) + if verbose: + print("C\n", str(C), "\n", err) + assert err < eps + + D = V * V.transpose_conj() - mp.eye(V.rows) + err = mp.mnorm(D) + if verbose: + print("D:\n", str(D), "\n", err) + assert err < eps + + E = U.transpose_conj() * U - mp.eye(U.cols) + err = mp.mnorm(E) + if verbose: + print("E:\n", str(E), "\n", err) + assert err < eps + +def run_gauss(qtype, a, b): + eps = 1e-5 + + d, e = mp.gauss_quadrature(len(a), qtype) + d -= mp.matrix(a) + e -= mp.matrix(b) + + assert mp.mnorm(d) < eps + assert mp.mnorm(e) < eps + +def irandmatrix(n, range = 10): + """ + random matrix with integer entries + """ + A = mp.matrix(n, n) + for i in xrange(n): + for j in xrange(n): + A[i,j]=int( (2 * mp.rand() - 1) * range) + return A + +####################### + +def test_eighe_fixed_matrix(): + A = mp.matrix([[2, 3], [3, 5]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[7, -11], [-11, 13]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[2, 11, 7], [11, 3, 13], [7, 13, 5]]) + run_eigsy(A) + run_eighe(A) + + A = mp.matrix([[2, 0, 7], [0, 3, 1], [7, 1, 5]]) + run_eigsy(A) + run_eighe(A) + + # + + A = mp.matrix([[2, 3+7j], [3-7j, 5]]) + run_eighe(A) + + A = mp.matrix([[2, -11j, 0], [+11j, 3, 29j], [0, -29j, 5]]) + run_eighe(A) + + A = mp.matrix([[2, 11 + 17j, 7 + 19j], [11 - 17j, 3, -13 + 23j], [7 - 19j, -13 - 23j, 5]]) + run_eighe(A) + +def test_eigsy_randmatrix(): + N = 5 + + for a in xrange(10): + A = 2 * mp.randmatrix(N, N) - 1 + + for i in xrange(0, N): + for j in xrange(i + 1, N): + A[j,i] = A[i,j] + + run_eigsy(A) + +def test_eighe_randmatrix(): + N = 5 + + for a in xrange(10): + A = (2 * mp.randmatrix(N, N) - 1) + 1j * (2 * mp.randmatrix(N, N) - 1) + + for i in xrange(0, N): + A[i,i] = mp.re(A[i,i]) + for j in xrange(i + 1, N): + A[j,i] = mp.conj(A[i,j]) + + run_eighe(A) + +def test_eigsy_irandmatrix(): + N = 4 + R = 4 + + for a in xrange(10): + A=irandmatrix(N, R) + + for i in xrange(0, N): + for j in xrange(i + 1, N): + A[j,i] = A[i,j] + + run_eigsy(A) + +def test_eighe_irandmatrix(): + N = 4 + R = 4 + + for a in xrange(10): + A=irandmatrix(N, R) + 1j * irandmatrix(N, R) + + for i in xrange(0, N): + A[i,i] = mp.re(A[i,i]) + for j in xrange(i + 1, N): + A[j,i] = mp.conj(A[i,j]) + + run_eighe(A) + +def test_svd_r_rand(): + for i in xrange(5): + full = mp.rand() > 0.5 + m = 1 + int(mp.rand() * 10) + n = 1 + int(mp.rand() * 10) + A = 2 * mp.randmatrix(m, n) - 1 + if mp.rand() > 0.5: + A *= 10 + for x in xrange(m): + for y in xrange(n): + A[x,y]=int(A[x,y]) + + run_svd_r(A, full_matrices = full, verbose = False) + +def test_svd_c_rand(): + for i in xrange(5): + full = mp.rand() > 0.5 + m = 1 + int(mp.rand() * 10) + n = 1 + int(mp.rand() * 10) + A = (2 * mp.randmatrix(m, n) - 1) + 1j * (2 * mp.randmatrix(m, n) - 1) + if mp.rand() > 0.5: + A *= 10 + for x in xrange(m): + for y in xrange(n): + A[x,y]=int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y])) + + run_svd_c(A, full_matrices=full, verbose=False) + +def test_svd_test_case(): + # a test case from Golub and Reinsch + # (see wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971).) + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + a = [[22, 10, 2, 3, 7], + [14, 7, 10, 0, 8], + [-1, 13, -1, -11, 3], + [-3, -2, 13, -2, 4], + [ 9, 8, 1, -2, 4], + [ 9, 1, -7, 5, -1], + [ 2, -6, 6, 5, 1], + [ 4, 5, 0, -2, 2]] + + a = mp.matrix(a) + b = mp.matrix([mp.sqrt(1248), 20, mp.sqrt(384), 0, 0]) + + S = mp.svd_r(a, compute_uv = False) + S -= b + assert mp.mnorm(S) < eps + + S = mp.svd_c(a, compute_uv = False) + S -= b + assert mp.mnorm(S) < eps + + +def test_gauss_quadrature_static(): + a = [-0.57735027, 0.57735027] + b = [ 1, 1] + run_gauss("legendre", a , b) + + a = [ -0.906179846, -0.538469310, 0, 0.538469310, 0.906179846] + b = [ 0.23692689, 0.47862867, 0.56888889, 0.47862867, 0.23692689] + run_gauss("legendre", a , b) + + a = [ 0.06943184, 0.33000948, 0.66999052, 0.93056816] + b = [ 0.17392742, 0.32607258, 0.32607258, 0.17392742] + run_gauss("legendre01", a , b) + + a = [-0.70710678, 0.70710678] + b = [ 0.88622693, 0.88622693] + run_gauss("hermite", a , b) + + a = [ -2.02018287, -0.958572465, 0, 0.958572465, 2.02018287] + b = [ 0.01995324, 0.39361932, 0.94530872, 0.39361932, 0.01995324] + run_gauss("hermite", a , b) + + a = [ 0.41577456, 2.29428036, 6.28994508] + b = [ 0.71109301, 0.27851773, 0.01038926] + run_gauss("laguerre", a , b) + +def test_gauss_quadrature_dynamic(verbose = False): + n = 5 + + A = mp.randmatrix(2 * n, 1) + + def F(x): + r = 0 + for i in xrange(len(A) - 1, -1, -1): + r = r * x + A[i] + return r + + def run(qtype, FW, R, alpha = 0, beta = 0): + X, W = mp.gauss_quadrature(n, qtype, alpha = alpha, beta = beta) + + a = 0 + for i in xrange(len(X)): + a += W[i] * F(X[i]) + + b = mp.quad(lambda x: FW(x) * F(x), R) + + c = mp.fabs(a - b) + + if verbose: + print(qtype, c, a, b) + + assert c < 1e-5 + + run("legendre", lambda x: 1, [-1, 1]) + run("legendre01", lambda x: 1, [0, 1]) + run("hermite", lambda x: mp.exp(-x*x), [-mp.inf, mp.inf]) + run("laguerre", lambda x: mp.exp(-x), [0, mp.inf]) + run("glaguerre", lambda x: mp.sqrt(x)*mp.exp(-x), [0, mp.inf], alpha = 1 / mp.mpf(2)) + run("chebyshev1", lambda x: 1/mp.sqrt(1-x*x), [-1, 1]) + run("chebyshev2", lambda x: mp.sqrt(1-x*x), [-1, 1]) + run("jacobi", lambda x: (1-x)**(1/mp.mpf(3)) * (1+x)**(1/mp.mpf(5)), [-1, 1], alpha = 1 / mp.mpf(3), beta = 1 / mp.mpf(5) ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_elliptic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_elliptic.py new file mode 100644 index 0000000000000000000000000000000000000000..4dddc2df34b8d2fa7f2028b3501e5b7f140d8912 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_elliptic.py @@ -0,0 +1,670 @@ +""" +Limited tests of the elliptic functions module. A full suite of +extensive testing can be found in elliptic_torture_tests.py + +Author of the first version: M.T. Taschuk + +References: + +[1] Abramowitz & Stegun. 'Handbook of Mathematical Functions, 9th Ed.', + (Dover duplicate of 1972 edition) +[2] Whittaker 'A Course of Modern Analysis, 4th Ed.', 1946, + Cambridge University Press + +""" + +import mpmath +import random +import pytest + +from mpmath import * + +def mpc_ae(a, b, eps=eps): + res = True + res = res and a.real.ae(b.real, eps) + res = res and a.imag.ae(b.imag, eps) + return res + +zero = mpf(0) +one = mpf(1) + +jsn = ellipfun('sn') +jcn = ellipfun('cn') +jdn = ellipfun('dn') + +calculate_nome = lambda k: qfrom(k=k) + +def test_ellipfun(): + mp.dps = 15 + assert ellipfun('ss', 0, 0) == 1 + assert ellipfun('cc', 0, 0) == 1 + assert ellipfun('dd', 0, 0) == 1 + assert ellipfun('nn', 0, 0) == 1 + assert ellipfun('sn', 0.25, 0).ae(sin(0.25)) + assert ellipfun('cn', 0.25, 0).ae(cos(0.25)) + assert ellipfun('dn', 0.25, 0).ae(1) + assert ellipfun('ns', 0.25, 0).ae(csc(0.25)) + assert ellipfun('nc', 0.25, 0).ae(sec(0.25)) + assert ellipfun('nd', 0.25, 0).ae(1) + assert ellipfun('sc', 0.25, 0).ae(tan(0.25)) + assert ellipfun('sd', 0.25, 0).ae(sin(0.25)) + assert ellipfun('cd', 0.25, 0).ae(cos(0.25)) + assert ellipfun('cs', 0.25, 0).ae(cot(0.25)) + assert ellipfun('dc', 0.25, 0).ae(sec(0.25)) + assert ellipfun('ds', 0.25, 0).ae(csc(0.25)) + assert ellipfun('sn', 0.25, 1).ae(tanh(0.25)) + assert ellipfun('cn', 0.25, 1).ae(sech(0.25)) + assert ellipfun('dn', 0.25, 1).ae(sech(0.25)) + assert ellipfun('ns', 0.25, 1).ae(coth(0.25)) + assert ellipfun('nc', 0.25, 1).ae(cosh(0.25)) + assert ellipfun('nd', 0.25, 1).ae(cosh(0.25)) + assert ellipfun('sc', 0.25, 1).ae(sinh(0.25)) + assert ellipfun('sd', 0.25, 1).ae(sinh(0.25)) + assert ellipfun('cd', 0.25, 1).ae(1) + assert ellipfun('cs', 0.25, 1).ae(csch(0.25)) + assert ellipfun('dc', 0.25, 1).ae(1) + assert ellipfun('ds', 0.25, 1).ae(csch(0.25)) + assert ellipfun('sn', 0.25, 0.5).ae(0.24615967096986145833) + assert ellipfun('cn', 0.25, 0.5).ae(0.96922928989378439337) + assert ellipfun('dn', 0.25, 0.5).ae(0.98473484156599474563) + assert ellipfun('ns', 0.25, 0.5).ae(4.0624038700573130369) + assert ellipfun('nc', 0.25, 0.5).ae(1.0317476065024692949) + assert ellipfun('nd', 0.25, 0.5).ae(1.0155017958029488665) + assert ellipfun('sc', 0.25, 0.5).ae(0.25397465134058993408) + assert ellipfun('sd', 0.25, 0.5).ae(0.24997558792415733063) + assert ellipfun('cd', 0.25, 0.5).ae(0.98425408443195497052) + assert ellipfun('cs', 0.25, 0.5).ae(3.9374008182374110826) + assert ellipfun('dc', 0.25, 0.5).ae(1.0159978158253033913) + assert ellipfun('ds', 0.25, 0.5).ae(4.0003906313579720593) + + + + +def test_calculate_nome(): + mp.dps = 100 + + q = calculate_nome(zero) + assert(q == zero) + + mp.dps = 25 + # used Mathematica's EllipticNomeQ[m] + math1 = [(mpf(1)/10, mpf('0.006584651553858370274473060')), + (mpf(2)/10, mpf('0.01394285727531826872146409')), + (mpf(3)/10, mpf('0.02227743615715350822901627')), + (mpf(4)/10, mpf('0.03188334731336317755064299')), + (mpf(5)/10, mpf('0.04321391826377224977441774')), + (mpf(6)/10, mpf('0.05702025781460967637754953')), + (mpf(7)/10, mpf('0.07468994353717944761143751')), + (mpf(8)/10, mpf('0.09927369733882489703607378')), + (mpf(9)/10, mpf('0.1401731269542615524091055')), + (mpf(9)/10, mpf('0.1401731269542615524091055'))] + + for i in math1: + m = i[0] + q = calculate_nome(sqrt(m)) + assert q.ae(i[1]) + + mp.dps = 15 + +def test_jtheta(): + mp.dps = 25 + + z = q = zero + for n in range(1,5): + value = jtheta(n, z, q) + assert(value == (n-1)//2) + + for q in [one, mpf(2)]: + for n in range(1,5): + pytest.raises(ValueError, lambda: jtheta(n, z, q)) + + z = one/10 + q = one/11 + + # Mathematical N[EllipticTheta[1, 1/10, 1/11], 25] + res = mpf('0.1069552990104042681962096') + result = jtheta(1, z, q) + assert(result.ae(res)) + + # Mathematica N[EllipticTheta[2, 1/10, 1/11], 25] + res = mpf('1.101385760258855791140606') + result = jtheta(2, z, q) + assert(result.ae(res)) + + # Mathematica N[EllipticTheta[3, 1/10, 1/11], 25] + res = mpf('1.178319743354331061795905') + result = jtheta(3, z, q) + assert(result.ae(res)) + + # Mathematica N[EllipticTheta[4, 1/10, 1/11], 25] + res = mpf('0.8219318954665153577314573') + result = jtheta(4, z, q) + assert(result.ae(res)) + + # test for sin zeros for jtheta(1, z, q) + # test for cos zeros for jtheta(2, z, q) + z1 = pi + z2 = pi/2 + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + result = jtheta(1, z1, q) + assert(result.ae(0)) + result = jtheta(2, z2, q) + assert(result.ae(0)) + mp.dps = 15 + + +def test_jtheta_issue_79(): + # near the circle of covergence |q| = 1 the convergence slows + # down; for |q| > Q_LIM the theta functions raise ValueError + mp.dps = 30 + mp.dps += 30 + q = mpf(6)/10 - one/10**6 - mpf(8)/10 * j + mp.dps -= 30 + # Mathematica run first + # N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 2000] + # then it works: + # N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 30] + res = mpf('32.0031009628901652627099524264') + \ + mpf('16.6153027998236087899308935624') * j + result = jtheta(3, 1, q) + # check that for abs(q) > Q_LIM a ValueError exception is raised + mp.dps += 30 + q = mpf(6)/10 - one/10**7 - mpf(8)/10 * j + mp.dps -= 30 + pytest.raises(ValueError, lambda: jtheta(3, 1, q)) + + # bug reported in issue 79 + mp.dps = 100 + z = (1+j)/3 + q = mpf(368983957219251)/10**15 + mpf(636363636363636)/10**15 * j + # Mathematica N[EllipticTheta[1, z, q], 35] + res = mpf('2.4439389177990737589761828991467471') + \ + mpf('0.5446453005688226915290954851851490') *j + mp.dps = 30 + result = jtheta(1, z, q) + assert(result.ae(res)) + mp.dps = 80 + z = 3 + 4*j + q = 0.5 + 0.5*j + r1 = jtheta(1, z, q) + mp.dps = 15 + r2 = jtheta(1, z, q) + assert r1.ae(r2) + mp.dps = 80 + z = 3 + j + q1 = exp(j*3) + # longer test + # for n in range(1, 6) + for n in range(1, 2): + mp.dps = 80 + q = q1*(1 - mpf(1)/10**n) + r1 = jtheta(1, z, q) + mp.dps = 15 + r2 = jtheta(1, z, q) + assert r1.ae(r2) + mp.dps = 15 + # issue 79 about high derivatives + assert jtheta(3, 4.5, 0.25, 9).ae(1359.04892680683) + assert jtheta(3, 4.5, 0.25, 50).ae(-6.14832772630905e+33) + mp.dps = 50 + r = jtheta(3, 4.5, 0.25, 9) + assert r.ae('1359.048926806828939547859396600218966947753213803') + r = jtheta(3, 4.5, 0.25, 50) + assert r.ae('-6148327726309051673317975084654262.4119215720343656') + +def test_jtheta_identities(): + """ + Tests the some of the jacobi identidies found in Abramowitz, + Sec. 16.28, Pg. 576. The identities are tested to 1 part in 10^98. + """ + mp.dps = 110 + eps1 = ldexp(eps, 30) + + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + + zstring = str(10*random.random()) + z = mpf(zstring) + # Abramowitz 16.28.1 + # v_1(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_2(0, q)**2 + # - v_2(z, q)**2 * v_3(0, q)**2 + term1 = (jtheta(1, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(3, z, q)**2) * (jtheta(2, zero, q)**2) + term3 = (jtheta(2, z, q)**2) * (jtheta(3, zero, q)**2) + equality = term1 - term2 + term3 + assert(equality.ae(0, eps1)) + + zstring = str(100*random.random()) + z = mpf(zstring) + # Abramowitz 16.28.2 + # v_2(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_2(0, q)**2 + # - v_1(z, q)**2 * v_3(0, q)**2 + term1 = (jtheta(2, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(4, z, q)**2) * (jtheta(2, zero, q)**2) + term3 = (jtheta(1, z, q)**2) * (jtheta(3, zero, q)**2) + equality = term1 - term2 + term3 + assert(equality.ae(0, eps1)) + + # Abramowitz 16.28.3 + # v_3(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_3(0, q)**2 + # - v_1(z, q)**2 * v_2(0, q)**2 + term1 = (jtheta(3, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(4, z, q)**2) * (jtheta(3, zero, q)**2) + term3 = (jtheta(1, z, q)**2) * (jtheta(2, zero, q)**2) + equality = term1 - term2 + term3 + assert(equality.ae(0, eps1)) + + # Abramowitz 16.28.4 + # v_4(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_3(0, q)**2 + # - v_2(z, q)**2 * v_2(0, q)**2 + term1 = (jtheta(4, z, q)**2) * (jtheta(4, zero, q)**2) + term2 = (jtheta(3, z, q)**2) * (jtheta(3, zero, q)**2) + term3 = (jtheta(2, z, q)**2) * (jtheta(2, zero, q)**2) + equality = term1 - term2 + term3 + assert(equality.ae(0, eps1)) + + # Abramowitz 16.28.5 + # v_2(0, q)**4 + v_4(0, q)**4 == v_3(0, q)**4 + term1 = (jtheta(2, zero, q))**4 + term2 = (jtheta(4, zero, q))**4 + term3 = (jtheta(3, zero, q))**4 + equality = term1 + term2 - term3 + assert(equality.ae(0, eps1)) + mp.dps = 15 + +def test_jtheta_complex(): + mp.dps = 30 + z = mpf(1)/4 + j/8 + q = mpf(1)/3 + j/7 + # Mathematica N[EllipticTheta[1, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.31618034835986160705729105731678285') + \ + mpf('0.07542013825835103435142515194358975') * j + r = jtheta(1, z, q) + assert(mpc_ae(r, res)) + + # Mathematica N[EllipticTheta[2, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('1.6530986428239765928634711417951828') + \ + mpf('0.2015344864707197230526742145361455') * j + r = jtheta(2, z, q) + assert(mpc_ae(r, res)) + + # Mathematica N[EllipticTheta[3, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('1.6520564411784228184326012700348340') + \ + mpf('0.1998129119671271328684690067401823') * j + r = jtheta(3, z, q) + assert(mpc_ae(r, res)) + + # Mathematica N[EllipticTheta[4, 1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.37619082382228348252047624089973824') - \ + mpf('0.15623022130983652972686227200681074') * j + r = jtheta(4, z, q) + assert(mpc_ae(r, res)) + + # check some theta function identities + mp.dos = 100 + z = mpf(1)/4 + j/8 + q = mpf(1)/3 + j/7 + mp.dps += 10 + a = [0,0, jtheta(2, 0, q), jtheta(3, 0, q), jtheta(4, 0, q)] + t = [0, jtheta(1, z, q), jtheta(2, z, q), jtheta(3, z, q), jtheta(4, z, q)] + r = [(t[2]*a[4])**2 - (t[4]*a[2])**2 + (t[1] *a[3])**2, + (t[3]*a[4])**2 - (t[4]*a[3])**2 + (t[1] *a[2])**2, + (t[1]*a[4])**2 - (t[3]*a[2])**2 + (t[2] *a[3])**2, + (t[4]*a[4])**2 - (t[3]*a[3])**2 + (t[2] *a[2])**2, + a[2]**4 + a[4]**4 - a[3]**4] + mp.dps -= 10 + for x in r: + assert(mpc_ae(x, mpc(0))) + mp.dps = 15 + +def test_djtheta(): + mp.dps = 30 + + z = one/7 + j/3 + q = one/8 + j/5 + # Mathematica N[EllipticThetaPrime[1, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('1.5555195883277196036090928995803201') - \ + mpf('0.02439761276895463494054149673076275') * j + result = jtheta(1, z, q, 1) + assert(mpc_ae(result, res)) + + # Mathematica N[EllipticThetaPrime[2, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('0.19825296689470982332701283509685662') - \ + mpf('0.46038135182282106983251742935250009') * j + result = jtheta(2, z, q, 1) + assert(mpc_ae(result, res)) + + # Mathematica N[EllipticThetaPrime[3, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('0.36492498415476212680896699407390026') - \ + mpf('0.57743812698666990209897034525640369') * j + result = jtheta(3, z, q, 1) + assert(mpc_ae(result, res)) + + # Mathematica N[EllipticThetaPrime[4, 1/7 + I/3, 1/8 + I/5], 35] + res = mpf('-0.38936892528126996010818803742007352') + \ + mpf('0.66549886179739128256269617407313625') * j + result = jtheta(4, z, q, 1) + assert(mpc_ae(result, res)) + + for i in range(10): + q = (one*random.random() + j*random.random())/2 + # identity in Wittaker, Watson &21.41 + a = jtheta(1, 0, q, 1) + b = jtheta(2, 0, q)*jtheta(3, 0, q)*jtheta(4, 0, q) + assert(a.ae(b)) + + # test higher derivatives + mp.dps = 20 + for q,z in [(one/3, one/5), (one/3 + j/8, one/5), + (one/3, one/5 + j/8), (one/3 + j/7, one/5 + j/8)]: + for n in [1, 2, 3, 4]: + r = jtheta(n, z, q, 2) + r1 = diff(lambda zz: jtheta(n, zz, q), z, n=2) + assert r.ae(r1) + r = jtheta(n, z, q, 3) + r1 = diff(lambda zz: jtheta(n, zz, q), z, n=3) + assert r.ae(r1) + + # identity in Wittaker, Watson &21.41 + q = one/3 + z = zero + a = [0]*5 + a[1] = jtheta(1, z, q, 3)/jtheta(1, z, q, 1) + for n in [2,3,4]: + a[n] = jtheta(n, z, q, 2)/jtheta(n, z, q) + equality = a[2] + a[3] + a[4] - a[1] + assert(equality.ae(0)) + mp.dps = 15 + +def test_jsn(): + """ + Test some special cases of the sn(z, q) function. + """ + mp.dps = 100 + + # trival case + result = jsn(zero, zero) + assert(result == zero) + + # Abramowitz Table 16.5 + # + # sn(0, m) = 0 + + for i in range(10): + qstring = str(random.random()) + q = mpf(qstring) + + equality = jsn(zero, q) + assert(equality.ae(0)) + + # Abramowitz Table 16.6.1 + # + # sn(z, 0) = sin(z), m == 0 + # + # sn(z, 1) = tanh(z), m == 1 + # + # It would be nice to test these, but I find that they run + # in to numerical trouble. I'm currently treating as a boundary + # case for sn function. + + mp.dps = 25 + arg = one/10 + #N[JacobiSN[1/10, 2^-100], 25] + res = mpf('0.09983341664682815230681420') + m = ldexp(one, -100) + result = jsn(arg, m) + assert(result.ae(res)) + + # N[JacobiSN[1/10, 1/10], 25] + res = mpf('0.09981686718599080096451168') + result = jsn(arg, arg) + assert(result.ae(res)) + mp.dps = 15 + +def test_jcn(): + """ + Test some special cases of the cn(z, q) function. + """ + mp.dps = 100 + + # Abramowitz Table 16.5 + # cn(0, q) = 1 + qstring = str(random.random()) + q = mpf(qstring) + cn = jcn(zero, q) + assert(cn.ae(one)) + + # Abramowitz Table 16.6.2 + # + # cn(u, 0) = cos(u), m == 0 + # + # cn(u, 1) = sech(z), m == 1 + # + # It would be nice to test these, but I find that they run + # in to numerical trouble. I'm currently treating as a boundary + # case for cn function. + + mp.dps = 25 + arg = one/10 + m = ldexp(one, -100) + #N[JacobiCN[1/10, 2^-100], 25] + res = mpf('0.9950041652780257660955620') + result = jcn(arg, m) + assert(result.ae(res)) + + # N[JacobiCN[1/10, 1/10], 25] + res = mpf('0.9950058256237368748520459') + result = jcn(arg, arg) + assert(result.ae(res)) + mp.dps = 15 + +def test_jdn(): + """ + Test some special cases of the dn(z, q) function. + """ + mp.dps = 100 + + # Abramowitz Table 16.5 + # dn(0, q) = 1 + mstring = str(random.random()) + m = mpf(mstring) + + dn = jdn(zero, m) + assert(dn.ae(one)) + + mp.dps = 25 + # N[JacobiDN[1/10, 1/10], 25] + res = mpf('0.9995017055025556219713297') + arg = one/10 + result = jdn(arg, arg) + assert(result.ae(res)) + mp.dps = 15 + + +def test_sn_cn_dn_identities(): + """ + Tests the some of the jacobi elliptic function identities found + on Mathworld. Haven't found in Abramowitz. + """ + mp.dps = 100 + N = 5 + for i in range(N): + qstring = str(random.random()) + q = mpf(qstring) + zstring = str(100*random.random()) + z = mpf(zstring) + + # MathWorld + # sn(z, q)**2 + cn(z, q)**2 == 1 + term1 = jsn(z, q)**2 + term2 = jcn(z, q)**2 + equality = one - term1 - term2 + assert(equality.ae(0)) + + # MathWorld + # k**2 * sn(z, m)**2 + dn(z, m)**2 == 1 + for i in range(N): + mstring = str(random.random()) + m = mpf(qstring) + k = m.sqrt() + zstring = str(10*random.random()) + z = mpf(zstring) + term1 = k**2 * jsn(z, m)**2 + term2 = jdn(z, m)**2 + equality = one - term1 - term2 + assert(equality.ae(0)) + + + for i in range(N): + mstring = str(random.random()) + m = mpf(mstring) + k = m.sqrt() + zstring = str(random.random()) + z = mpf(zstring) + + # MathWorld + # k**2 * cn(z, m)**2 + (1 - k**2) = dn(z, m)**2 + term1 = k**2 * jcn(z, m)**2 + term2 = 1 - k**2 + term3 = jdn(z, m)**2 + equality = term3 - term1 - term2 + assert(equality.ae(0)) + + K = ellipk(k**2) + # Abramowitz Table 16.5 + # sn(K, m) = 1; K is K(k), first complete elliptic integral + r = jsn(K, m) + assert(r.ae(one)) + + # Abramowitz Table 16.5 + # cn(K, q) = 0; K is K(k), first complete elliptic integral + equality = jcn(K, m) + assert(equality.ae(0)) + + # Abramowitz Table 16.6.3 + # dn(z, 0) = 1, m == 0 + z = m + value = jdn(z, zero) + assert(value.ae(one)) + + mp.dps = 15 + +def test_sn_cn_dn_complex(): + mp.dps = 30 + # N[JacobiSN[1/4 + I/8, 1/3 + I/7], 35] in Mathematica + res = mpf('0.2495674401066275492326652143537') + \ + mpf('0.12017344422863833381301051702823') * j + u = mpf(1)/4 + j/8 + m = mpf(1)/3 + j/7 + r = jsn(u, m) + assert(mpc_ae(r, res)) + + #N[JacobiCN[1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.9762691700944007312693721148331') - \ + mpf('0.0307203994181623243583169154824')*j + r = jcn(u, m) + #assert r.real.ae(res.real) + #assert r.imag.ae(res.imag) + assert(mpc_ae(r, res)) + + #N[JacobiDN[1/4 + I/8, 1/3 + I/7], 35] + res = mpf('0.99639490163039577560547478589753039') - \ + mpf('0.01346296520008176393432491077244994')*j + r = jdn(u, m) + assert(mpc_ae(r, res)) + mp.dps = 15 + +def test_elliptic_integrals(): + # Test cases from Carlson's paper + mp.dps = 15 + assert elliprd(0,2,1).ae(1.7972103521033883112) + assert elliprd(2,3,4).ae(0.16510527294261053349) + assert elliprd(j,-j,2).ae(0.65933854154219768919) + assert elliprd(0,j,-j).ae(1.2708196271909686299 + 2.7811120159520578777j) + assert elliprd(0,j-1,j).ae(-1.8577235439239060056 - 0.96193450888838559989j) + assert elliprd(-2-j,-j,-1+j).ae(1.8249027393703805305 - 1.2218475784827035855j) + # extra test cases + assert elliprg(0,0,0) == 0 + assert elliprg(0,0,16).ae(2) + assert elliprg(0,16,0).ae(2) + assert elliprg(16,0,0).ae(2) + assert elliprg(1,4,0).ae(1.2110560275684595248036) + assert elliprg(1,0,4).ae(1.2110560275684595248036) + assert elliprg(0,4,1).ae(1.2110560275684595248036) + # should be symmetric -- fixes a bug present in the paper + x,y,z = 1,1j,-1+1j + assert elliprg(x,y,z).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(x,z,y).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(y,x,z).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(y,z,x).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(z,x,y).ae(0.64139146875812627545 + 0.58085463774808290907j) + assert elliprg(z,y,x).ae(0.64139146875812627545 + 0.58085463774808290907j) + + for n in [5, 15, 30, 60, 100]: + mp.dps = n + assert elliprf(1,2,0).ae('1.3110287771460599052324197949455597068413774757158115814084108519003952935352071251151477664807145467230678763') + assert elliprf(0.5,1,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871') + assert elliprf(j,-j,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871') + assert elliprf(j-1,j,0).ae(mpc('0.79612586584233913293056938229563057846592264089185680214929401744498956943287031832657642790719940442165621412', + '-1.2138566698364959864300942567386038975419875860741507618279563735753073152507112254567291141460317931258599889')) + assert elliprf(2,3,4).ae('0.58408284167715170669284916892566789240351359699303216166309375305508295130412919665541330837704050454472379308') + assert elliprf(j,-j,2).ae('1.0441445654064360931078658361850779139591660747973017593275012615517220315993723776182276555339288363064476126') + assert elliprf(j-1,j,1-j).ae(mpc('0.93912050218619371196624617169781141161485651998254431830645241993282941057500174238125105410055253623847335313', + '-0.53296252018635269264859303449447908970360344322834582313172115220559316331271520508208025270300138589669326136')) + assert elliprc(0,0.25).ae(+pi) + assert elliprc(2.25,2).ae(+ln2) + assert elliprc(0,j).ae(mpc('1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532', + '-1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532')) + assert elliprc(-j,j).ae(mpc('1.2260849569072198222319655083097718755633725139745941606203839524036426936825652935738621522906572884239069297', + '-0.34471136988767679699935618332997956653521218571295874986708834375026550946053920574015526038040124556716711353')) + assert elliprc(0.25,-2).ae(ln2/3) + assert elliprc(j,-1).ae(mpc('0.77778596920447389875196055840799837589537035343923012237628610795937014001905822029050288316217145443865649819', + '0.1983248499342877364755170948292130095921681309577950696116251029742793455964385947473103628983664877025779304')) + assert elliprj(0,1,2,3).ae('0.77688623778582332014190282640545501102298064276022952731669118325952563819813258230708177398475643634103990878') + assert elliprj(2,3,4,5).ae('0.14297579667156753833233879421985774801466647854232626336218889885463800128817976132826443904216546421431528308') + assert elliprj(2,3,4,-1+j).ae(mpc('0.13613945827770535203521374457913768360237593025944342652613569368333226052158214183059386307242563164036672709', + '-0.38207561624427164249600936454845112611060375760094156571007648297226090050927156176977091273224510621553615189')) + assert elliprj(j,-j,0,2).ae('1.6490011662710884518243257224860232300246792717163891216346170272567376981346412066066050103935109581019055806') + assert elliprj(-1+j,-1-j,1,2).ae('0.94148358841220238083044612133767270187474673547917988681610772381758628963408843935027667916713866133196845063') + assert elliprj(j,-j,0,1-j).ae(mpc('1.8260115229009316249372594065790946657011067182850435297162034335356430755397401849070610280860044610878657501', + '1.2290661908643471500163617732957042849283739403009556715926326841959667290840290081010472716420690899886276961')) + assert elliprj(-1+j,-1-j,1,-3+j).ae(mpc('-0.61127970812028172123588152373622636829986597243716610650831553882054127570542477508023027578037045504958619422', + '-1.0684038390006807880182112972232562745485871763154040245065581157751693730095703406209466903752930797510491155')) + assert elliprj(-1+j,-2-j,-j,-1+j).ae(mpc('1.8249027393703805304622013339009022294368078659619988943515764258335975852685224202567854526307030593012768954', + '-1.2218475784827035854568450371590419833166777535029296025352291308244564398645467465067845461070602841312456831')) + + assert elliprg(0,16,16).ae(+pi) + assert elliprg(2,3,4).ae('1.7255030280692277601061148835701141842692457170470456590515892070736643637303053506944907685301315299153040991') + assert elliprg(0,j,-j).ae('0.42360654239698954330324956174109581824072295516347109253028968632986700241706737986160014699730561497106114281') + assert elliprg(j-1,j,0).ae(mpc('0.44660591677018372656731970402124510811555212083508861036067729944477855594654762496407405328607219895053798354', + '0.70768352357515390073102719507612395221369717586839400605901402910893345301718731499237159587077682267374159282')) + assert elliprg(-j,j-1,j).ae(mpc('0.36023392184473309033675652092928695596803358846377334894215349632203382573844427952830064383286995172598964266', + '0.40348623401722113740956336997761033878615232917480045914551915169013722542827052849476969199578321834819903921')) + assert elliprg(0, mpf('0.0796'), 4).ae('1.0284758090288040009838871385180217366569777284430590125081211090574701293154645750017813190805144572673802094') + mp.dps = 15 + + # more test cases for the branch of ellippi / elliprj + assert elliprj(-1-0.5j, -10-6j, -10-3j, -5+10j).ae(0.128470516743927699 + 0.102175950778504625j, abs_eps=1e-8) + assert elliprj(1.987, 4.463 - 1.614j, 0, -3.965).ae(-0.341575118513811305 - 0.394703757004268486j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037+0.632j, 1.654, -0.9609).ae(-1.14735199581485639 - 0.134450158867472264j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037-0.632j, 1.654, -0.9609).ae(1.758765901861727 - 0.161002343366626892j, abs_eps=1e-5) + assert elliprj(0.3068, -4.037+0.0632j, 1.654, -0.9609).ae(-1.17157627949475577 - 0.069182614173988811j, abs_eps=1e-8) + assert elliprj(0.3068, -4.037+0.00632j, 1.654, -0.9609).ae(-1.17337595670549633 - 0.0623069224526925j, abs_eps=1e-8) + + # these require accurate integration + assert elliprj(0.3068, -4.037-0.0632j, 1.654, -0.9609).ae(1.77940452391261626 + 0.0388711305592447234j) + assert elliprj(0.3068, -4.037-0.00632j, 1.654, -0.9609).ae(1.77806722756403055 + 0.0592749824572262329j) + # issue #571 + assert ellippi(2.1 + 0.94j, 2.3 + 0.98j, 2.5 + 0.01j).ae(-0.40652414240811963438 + 2.1547659461404749309j) + + assert ellippi(2.0-1.0j, 2.0+1.0j).ae(1.8578723151271115 - 1.18642180609983531j) + assert ellippi(2.0-0.5j, 0.5+1.0j).ae(0.936761970766645807 - 1.61876787838890786j) + assert ellippi(2.0, 1.0+1.0j).ae(0.999881420735506708 - 2.4139272867045391j) + assert ellippi(2.0+1.0j, 2.0-1.0j).ae(1.8578723151271115 + 1.18642180609983531j) + assert ellippi(2.0+1.0j, 2.0).ae(2.78474654927885845 + 2.02204728966993314j) + +def test_issue_238(): + assert isnan(qfrom(m=nan)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_fp.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_fp.py new file mode 100644 index 0000000000000000000000000000000000000000..99f3759c3071c4d55e0481472f3d16c1f5df1fef --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_fp.py @@ -0,0 +1,1671 @@ +""" +Easy-to-use test-generating code: + +cases = ''' +exp 2.25 +log 2.25 +''' + +from mpmath import * +mp.dps = 20 +for test in cases.splitlines(): + if not test: + continue + words = test.split() + fname = words[0] + args = words[1:] + argstr = ", ".join(args) + testline = "%s(%s)" % (fname, argstr) + ans = str(eval(testline)) + print " assert ae(fp.%s, %s)" % (testline, ans) + +""" + +from mpmath import fp + +def ae(x, y, tol=1e-12): + if x == y: + return True + return abs(x-y) <= tol*abs(y) + +def test_conj(): + assert fp.conj(4) == 4 + assert fp.conj(3+4j) == 3-4j + assert fp.fdot([1,2],[3,2+1j], conjugate=True) == 7-2j + +def test_fp_number_parts(): + assert ae(fp.arg(3), 0.0) + assert ae(fp.arg(-3), 3.1415926535897932385) + assert ae(fp.arg(3j), 1.5707963267948966192) + assert ae(fp.arg(-3j), -1.5707963267948966192) + assert ae(fp.arg(2+3j), 0.98279372324732906799) + assert ae(fp.arg(-1-1j), -2.3561944901923449288) + assert ae(fp.re(2.5), 2.5) + assert ae(fp.re(2.5+3j), 2.5) + assert ae(fp.im(2.5), 0.0) + assert ae(fp.im(2.5+3j), 3.0) + assert ae(fp.floor(2.5), 2.0) + assert ae(fp.floor(2), 2.0) + assert ae(fp.floor(2.0+0j), (2.0 + 0.0j)) + assert ae(fp.floor(-1.5-0.5j), (-2.0 - 1.0j)) + assert ae(fp.ceil(2.5), 3.0) + assert ae(fp.ceil(2), 2.0) + assert ae(fp.ceil(2.0+0j), (2.0 + 0.0j)) + assert ae(fp.ceil(-1.5-0.5j), (-1.0 + 0.0j)) + +def test_fp_cospi_sinpi(): + assert ae(fp.sinpi(0), 0.0) + assert ae(fp.sinpi(0.25), 0.7071067811865475244) + assert ae(fp.sinpi(0.5), 1.0) + assert ae(fp.sinpi(0.75), 0.7071067811865475244) + assert ae(fp.sinpi(1), 0.0) + assert ae(fp.sinpi(1.25), -0.7071067811865475244) + assert ae(fp.sinpi(1.5), -1.0) + assert ae(fp.sinpi(1.75), -0.7071067811865475244) + assert ae(fp.sinpi(2), 0.0) + assert ae(fp.sinpi(2.25), 0.7071067811865475244) + assert ae(fp.sinpi(0+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.sinpi(0.25+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(0.5+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.sinpi(0.75+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.sinpi(1+3j), (0.0 - 6195.8238636085899556j)) + assert ae(fp.sinpi(1.25+3j), (-4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.sinpi(1.5+3j), (-6195.8239443081075259 + 0.0j)) + assert ae(fp.sinpi(1.75+3j), (-4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(2+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.sinpi(2.25+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.sinpi(-0.75), -0.7071067811865475244) + assert ae(fp.sinpi(-1e-10), -3.1415926535897933529e-10) + assert ae(fp.sinpi(1e-10), 3.1415926535897933529e-10) + assert ae(fp.sinpi(1e-10+1e-10j), (3.141592653589793353e-10 + 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(1e-10-1e-10j), (3.141592653589793353e-10 - 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(-1e-10+1e-10j), (-3.141592653589793353e-10 + 3.1415926535897933528e-10j)) + assert ae(fp.sinpi(-1e-10-1e-10j), (-3.141592653589793353e-10 - 3.1415926535897933528e-10j)) + assert ae(fp.cospi(0), 1.0) + assert ae(fp.cospi(0.25), 0.7071067811865475244) + assert ae(fp.cospi(0.5), 0.0) + assert ae(fp.cospi(0.75), -0.7071067811865475244) + assert ae(fp.cospi(1), -1.0) + assert ae(fp.cospi(1.25), -0.7071067811865475244) + assert ae(fp.cospi(1.5), 0.0) + assert ae(fp.cospi(1.75), 0.7071067811865475244) + assert ae(fp.cospi(2), 1.0) + assert ae(fp.cospi(2.25), 0.7071067811865475244) + assert ae(fp.cospi(0+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(0.25+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(0.5+3j), (0.0 - 6195.8238636085899556j)) + assert ae(fp.cospi(0.75+3j), (-4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(1+3j), (-6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(1.25+3j), (-4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.cospi(1.5+3j), (0.0 + 6195.8238636085899556j)) + assert ae(fp.cospi(1.75+3j), (4381.1091260582448033 + 4381.1090689950686908j)) + assert ae(fp.cospi(2+3j), (6195.8239443081075259 + 0.0j)) + assert ae(fp.cospi(2.25+3j), (4381.1091260582448033 - 4381.1090689950686908j)) + assert ae(fp.cospi(-0.75), -0.7071067811865475244) + assert ae(fp.sinpi(-0.7), -0.80901699437494750611) + assert ae(fp.cospi(-0.7), -0.5877852522924730163) + assert ae(fp.cospi(-3+2j), (-267.74676148374822225 + 0.0j)) + assert ae(fp.sinpi(-3+2j), (0.0 - 267.74489404101651426j)) + assert ae(fp.sinpi(-0.7+2j), (-216.6116802292079471 - 157.37650009392034693j)) + assert ae(fp.cospi(-0.7+2j), (-157.37759774921754565 + 216.61016943630197336j)) + +def test_fp_expj(): + assert ae(fp.expj(0), (1.0 + 0.0j)) + assert ae(fp.expj(1), (0.5403023058681397174 + 0.84147098480789650665j)) + assert ae(fp.expj(2), (-0.416146836547142387 + 0.9092974268256816954j)) + assert ae(fp.expj(0.75), (0.73168886887382088631 + 0.68163876002333416673j)) + assert ae(fp.expj(2+3j), (-0.020718731002242879378 + 0.045271253156092975488j)) + assert ae(fp.expjpi(0), (1.0 + 0.0j)) + assert ae(fp.expjpi(1), (-1.0 + 0.0j)) + assert ae(fp.expjpi(2), (1.0 + 0.0j)) + assert ae(fp.expjpi(0.75), (-0.7071067811865475244 + 0.7071067811865475244j)) + assert ae(fp.expjpi(2+3j), (0.000080699517570304599239 + 0.0j)) + +def test_fp_bernoulli(): + assert ae(fp.bernoulli(0), 1.0) + assert ae(fp.bernoulli(1), -0.5) + assert ae(fp.bernoulli(2), 0.16666666666666666667) + assert ae(fp.bernoulli(10), 0.075757575757575757576) + assert ae(fp.bernoulli(11), 0.0) + +def test_fp_gamma(): + assert ae(fp.gamma(1), 1.0) + assert ae(fp.gamma(1.5), 0.88622692545275801365) + assert ae(fp.gamma(10), 362880.0) + assert ae(fp.gamma(-0.5), -3.5449077018110320546) + assert ae(fp.gamma(-7.1), 0.0016478244570263333622) + assert ae(fp.gamma(12.3), 83385367.899970000963) + assert ae(fp.gamma(2+0j), (1.0 + 0.0j)) + assert ae(fp.gamma(-2.5+0j), (-0.94530872048294188123 + 0.0j)) + assert ae(fp.gamma(3+4j), (0.0052255384713692141947 - 0.17254707929430018772j)) + assert ae(fp.gamma(-3-4j), (0.00001460997305874775607 - 0.000020760733311509070396j)) + assert ae(fp.fac(0), 1.0) + assert ae(fp.fac(1), 1.0) + assert ae(fp.fac(20), 2432902008176640000.0) + assert ae(fp.fac(-3.5), -0.94530872048294188123) + assert ae(fp.fac(2+3j), (-0.44011340763700171113 - 0.06363724312631702183j)) + assert ae(fp.loggamma(1.0), 0.0) + assert ae(fp.loggamma(2.0), 0.0) + assert ae(fp.loggamma(3.0), 0.69314718055994530942) + assert ae(fp.loggamma(7.25), 7.0521854507385394449) + assert ae(fp.loggamma(1000.0), 5905.2204232091812118) + assert ae(fp.loggamma(1e50), 1.1412925464970229298e+52) + assert ae(fp.loggamma(1e25+1e25j), (5.6125802751733671621e+26 + 5.7696599078528568383e+26j)) + assert ae(fp.loggamma(3+4j), (-1.7566267846037841105 + 4.7426644380346579282j)) + assert ae(fp.loggamma(-0.5), (1.2655121234846453965 - 3.1415926535897932385j)) + assert ae(fp.loggamma(-1.25), (1.3664317612369762346 - 6.2831853071795864769j)) + assert ae(fp.loggamma(-2.75), (0.0044878975359557733115 - 9.4247779607693797154j)) + assert ae(fp.loggamma(-3.5), (-1.3090066849930420464 - 12.566370614359172954j)) + assert ae(fp.loggamma(-4.5), (-2.8130840817693161197 - 15.707963267948966192j)) + assert ae(fp.loggamma(-2+3j), (-6.776523813485657093 - 4.568791367260286402j)) + assert ae(fp.loggamma(-1000.3), (-5912.8440347785205041 - 3144.7342462433830317j)) + assert ae(fp.loggamma(-100-100j), (-632.35117666833135562 - 158.37641469650352462j)) + assert ae(fp.loggamma(1e-10), 23.025850929882735237) + assert ae(fp.loggamma(-1e-10), (23.02585092999817837 - 3.1415926535897932385j)) + assert ae(fp.loggamma(1e-10j), (23.025850929940456804 - 1.5707963268526181857j)) + assert ae(fp.loggamma(1e-10j-1e-10), (22.679277339718205716 - 2.3561944902500664954j)) + +def test_fp_psi(): + assert ae(fp.psi(0, 3.7), 1.1671535393615114409) + assert ae(fp.psi(0, 0.5), -1.9635100260214234794) + assert ae(fp.psi(0, 1), -0.57721566490153286061) + assert ae(fp.psi(0, -2.5), 1.1031566406452431872) + assert ae(fp.psi(0, 12.9), 2.5179671503279156347) + assert ae(fp.psi(0, 100), 4.6001618527380874002) + assert ae(fp.psi(0, 2500.3), 7.8239660143238547877) + assert ae(fp.psi(0, 1e40), 92.103403719761827391) + assert ae(fp.psi(0, 1e200), 460.51701859880913677) + assert ae(fp.psi(0, 3.7+0j), (1.1671535393615114409 + 0.0j)) + assert ae(fp.psi(1, 3), 0.39493406684822643647) + assert ae(fp.psi(3, 2+3j), (-0.05383196209159972116 + 0.0076890935247364805218j)) + assert ae(fp.psi(4, -0.5+1j), (1.2719531355492328195 - 18.211833410936276774j)) + assert ae(fp.harmonic(0), 0.0) + assert ae(fp.harmonic(1), 1.0) + assert ae(fp.harmonic(2), 1.5) + assert ae(fp.harmonic(100), 5.1873775176396202608) + assert ae(fp.harmonic(-2.5), 1.2803723055467760478) + assert ae(fp.harmonic(2+3j), (1.9390425294578375875 + 0.87336044981834544043j)) + assert ae(fp.harmonic(-5-4j), (2.3725754822349437733 - 2.4160904444801621j)) + +def test_fp_zeta(): + assert ae(fp.zeta(1e100), 1.0) + assert ae(fp.zeta(3), 1.2020569031595942854) + assert ae(fp.zeta(2+0j), (1.6449340668482264365 + 0.0j)) + assert ae(fp.zeta(0.93), -13.713619351638164784) + assert ae(fp.zeta(1.74), 1.9796863545771774095) + assert ae(fp.zeta(0.0), -0.5) + assert ae(fp.zeta(-1.0), -0.083333333333333333333) + assert ae(fp.zeta(-2.0), 0.0) + assert ae(fp.zeta(-3.0), 0.0083333333333333333333) + assert ae(fp.zeta(-500.0), 0.0) + assert ae(fp.zeta(-7.4), 0.0036537321227995882447) + assert ae(fp.zeta(2.1), 1.5602165335033620158) + assert ae(fp.zeta(26.9), 1.0000000079854809935) + assert ae(fp.zeta(26), 1.0000000149015548284) + assert ae(fp.zeta(27), 1.0000000074507117898) + assert ae(fp.zeta(28), 1.0000000037253340248) + assert ae(fp.zeta(27.1), 1.000000006951755045) + assert ae(fp.zeta(32.7), 1.0000000001433243232) + assert ae(fp.zeta(100), 1.0) + assert ae(fp.altzeta(3.5), 0.92755357777394803511) + assert ae(fp.altzeta(1), 0.69314718055994530942) + assert ae(fp.altzeta(2), 0.82246703342411321824) + assert ae(fp.altzeta(0), 0.5) + assert ae(fp.zeta(-2+3j, 1), (0.13297115587929864827 + 0.12305330040458776494j)) + assert ae(fp.zeta(-2+3j, 5), (18.384866151867576927 - 11.377015110597711009j)) + assert ae(fp.zeta(1.0000000001), 9999999173.1735741337) + assert ae(fp.zeta(0.9999999999), -9999999172.0191428039) + assert ae(fp.zeta(1+0.000000001j), (0.57721566490153286061 - 999999999.99999993765j)) + assert ae(fp.primezeta(2.5+4j), (-0.16922458243438033385 - 0.010847965298387727811j)) + assert ae(fp.primezeta(4), 0.076993139764246844943) + assert ae(fp.riemannr(3.7), 2.3034079839110855717) + assert ae(fp.riemannr(8), 3.9011860449341499474) + assert ae(fp.riemannr(3+4j), (2.2369653314259991796 + 1.6339943856990281694j)) + +def test_fp_hyp2f1(): + assert ae(fp.hyp2f1(1, (3,2), 3.25, 5.0), (-0.46600275923108143059 - 0.74393667908854842325j)) + assert ae(fp.hyp2f1(1+1j, (3,2), 3.25, 5.0), (-5.9208875603806515987 - 2.3813557707889590686j)) + assert ae(fp.hyp2f1(1+1j, (3,2), 3.25, 2+3j), (0.17174552030925080445 + 0.19589781970539389999j)) + +def test_fp_erf(): + assert fp.erf(2) == fp.erf(2.0) == fp.erf(2.0+0.0j) + assert fp.erf(fp.inf) == 1.0 + assert fp.erf(fp.ninf) == -1.0 + assert ae(fp.erf(0), 0.0) + assert ae(fp.erf(-0), -0.0) + assert ae(fp.erf(0.3), 0.32862675945912741619) + assert ae(fp.erf(-0.3), -0.32862675945912741619) + assert ae(fp.erf(0.9), 0.79690821242283213966) + assert ae(fp.erf(-0.9), -0.79690821242283213966) + assert ae(fp.erf(1.0), 0.84270079294971486934) + assert ae(fp.erf(-1.0), -0.84270079294971486934) + assert ae(fp.erf(1.1), 0.88020506957408172966) + assert ae(fp.erf(-1.1), -0.88020506957408172966) + assert ae(fp.erf(8.5), 1.0) + assert ae(fp.erf(-8.5), -1.0) + assert ae(fp.erf(9.1), 1.0) + assert ae(fp.erf(-9.1), -1.0) + assert ae(fp.erf(20.0), 1.0) + assert ae(fp.erf(-20.0), -1.0) + assert ae(fp.erf(10000.0), 1.0) + assert ae(fp.erf(-10000.0), -1.0) + assert ae(fp.erf(1e+50), 1.0) + assert ae(fp.erf(-1e+50), -1.0) + assert ae(fp.erf(1j), 1.650425758797542876j) + assert ae(fp.erf(-1j), -1.650425758797542876j) + assert ae(fp.erf((2+3j)), (-20.829461427614568389 + 8.6873182714701631444j)) + assert ae(fp.erf(-(2+3j)), -(-20.829461427614568389 + 8.6873182714701631444j)) + assert ae(fp.erf((8+9j)), (-1072004.2525062051158 + 364149.91954310255423j)) + assert ae(fp.erf(-(8+9j)), -(-1072004.2525062051158 + 364149.91954310255423j)) + assert fp.erfc(fp.inf) == 0.0 + assert fp.erfc(fp.ninf) == 2.0 + assert fp.erfc(0) == 1 + assert fp.erfc(-0.0) == 1 + assert fp.erfc(0+0j) == 1 + assert ae(fp.erfc(0.3), 0.67137324054087258381) + assert ae(fp.erfc(-0.3), 1.3286267594591274162) + assert ae(fp.erfc(0.9), 0.20309178757716786034) + assert ae(fp.erfc(-0.9), 1.7969082124228321397) + assert ae(fp.erfc(1.0), 0.15729920705028513066) + assert ae(fp.erfc(-1.0), 1.8427007929497148693) + assert ae(fp.erfc(1.1), 0.11979493042591827034) + assert ae(fp.erfc(-1.1), 1.8802050695740817297) + assert ae(fp.erfc(8.5), 2.7623240713337714461e-33) + assert ae(fp.erfc(-8.5), 2.0) + assert ae(fp.erfc(9.1), 6.6969004279886077452e-38) + assert ae(fp.erfc(-9.1), 2.0) + assert ae(fp.erfc(20.0), 5.3958656116079009289e-176) + assert ae(fp.erfc(-20.0), 2.0) + assert ae(fp.erfc(10000.0), 0.0) + assert ae(fp.erfc(-10000.0), 2.0) + assert ae(fp.erfc(1e+50), 0.0) + assert ae(fp.erfc(-1e+50), 2.0) + assert ae(fp.erfc(1j), (1.0 - 1.650425758797542876j)) + assert ae(fp.erfc(-1j), (1.0 + 1.650425758797542876j)) + assert ae(fp.erfc((2+3j)), (21.829461427614568389 - 8.6873182714701631444j), 1e-13) + assert ae(fp.erfc(-(2+3j)), (-19.829461427614568389 + 8.6873182714701631444j), 1e-13) + assert ae(fp.erfc((8+9j)), (1072005.2525062051158 - 364149.91954310255423j)) + assert ae(fp.erfc(-(8+9j)), (-1072003.2525062051158 + 364149.91954310255423j)) + assert ae(fp.erfc(20+0j), (5.3958656116079009289e-176 + 0.0j)) + +def test_fp_lambertw(): + assert ae(fp.lambertw(0.0), 0.0) + assert ae(fp.lambertw(1.0), 0.567143290409783873) + assert ae(fp.lambertw(7.5), 1.5662309537823875394) + assert ae(fp.lambertw(-0.25), -0.35740295618138890307) + assert ae(fp.lambertw(-10.0), (1.3699809685212708156 + 2.140194527074713196j)) + assert ae(fp.lambertw(0+0j), (0.0 + 0.0j)) + assert ae(fp.lambertw(4+0j), (1.2021678731970429392 + 0.0j)) + assert ae(fp.lambertw(1000.5), 5.2500227450408980127) + assert ae(fp.lambertw(1e100), 224.84310644511850156) + assert ae(fp.lambertw(-1000.0), (5.1501630246362515223 + 2.6641981432905204596j)) + assert ae(fp.lambertw(1e-10), 9.9999999990000003645e-11) + assert ae(fp.lambertw(1e-10j), (1.0000000000000000728e-20 + 1.0000000000000000364e-10j)) + assert ae(fp.lambertw(3+4j), (1.2815618061237758782 + 0.53309522202097107131j)) + assert ae(fp.lambertw(-3-4j), (1.0750730665692549276 - 1.3251023817343588823j)) + assert ae(fp.lambertw(10000+1000j), (7.2361526563371602186 + 0.087567810943839352034j)) + assert ae(fp.lambertw(0.0, -1), -fp.inf) + assert ae(fp.lambertw(1.0, -1), (-1.5339133197935745079 - 4.3751851530618983855j)) + assert ae(fp.lambertw(7.5, -1), (0.44125668415098614999 - 4.8039842008452390179j)) + assert ae(fp.lambertw(-0.25, -1), -2.1532923641103496492) + assert ae(fp.lambertw(-10.0, -1), (1.3699809685212708156 - 2.140194527074713196j)) + assert ae(fp.lambertw(0+0j, -1), -fp.inf) + assert ae(fp.lambertw(4+0j, -1), (-0.15730793189620765317 - 4.6787800704666656212j)) + assert ae(fp.lambertw(1000.5, -1), (4.9153765415404024736 - 5.4465682700815159569j)) + assert ae(fp.lambertw(1e100, -1), (224.84272130101601052 - 6.2553713838167244141j)) + assert ae(fp.lambertw(-1000.0, -1), (5.1501630246362515223 - 2.6641981432905204596j)) + assert ae(fp.lambertw(1e-10, -1), (-26.303186778379041521 - 3.2650939117038283975j)) + assert ae(fp.lambertw(1e-10j, -1), (-26.297238779529035028 - 1.6328071613455765135j)) + assert ae(fp.lambertw(3+4j, -1), (0.25856740686699741676 - 3.8521166861614355895j)) + assert ae(fp.lambertw(-3-4j, -1), (-0.32028750204310768396 - 6.8801677192091972343j)) + assert ae(fp.lambertw(10000+1000j, -1), (7.0255308742285435567 - 5.5177506835734067601j)) + assert ae(fp.lambertw(0.0, 2), -fp.inf) + assert ae(fp.lambertw(1.0, 2), (-2.4015851048680028842 + 10.776299516115070898j)) + assert ae(fp.lambertw(7.5, 2), (-0.38003357962843791529 + 10.960916473368746184j)) + assert ae(fp.lambertw(-0.25, 2), (-4.0558735269061511898 + 13.852334658567271386j)) + assert ae(fp.lambertw(-10.0, 2), (-0.34479123764318858696 + 14.112740596763592363j)) + assert ae(fp.lambertw(0+0j, 2), -fp.inf) + assert ae(fp.lambertw(4+0j, 2), (-1.0070343323804262788 + 10.903476551861683082j)) + assert ae(fp.lambertw(1000.5, 2), (4.4076185165459395295 + 11.365524591091402177j)) + assert ae(fp.lambertw(1e100, 2), (224.84156762724875878 + 12.510785262632255672j)) + assert ae(fp.lambertw(-1000.0, 2), (4.1984245610246530756 + 14.420478573754313845j)) + assert ae(fp.lambertw(1e-10, 2), (-26.362258095445866488 + 9.7800247407031482519j)) + assert ae(fp.lambertw(1e-10j, 2), (-26.384250801683084252 + 11.403535950607739763j)) + assert ae(fp.lambertw(3+4j, 2), (-0.86554679943333993562 + 11.849956798331992027j)) + assert ae(fp.lambertw(-3-4j, 2), (-0.55792273874679112639 + 8.7173627024159324811j)) + assert ae(fp.lambertw(10000+1000j, 2), (6.6223802254585662734 + 11.61348646825020766j)) + +def test_fp_stress_ei_e1(): + # Can be tightened on recent Pythons with more accurate math/cmath + ATOL = 1e-13 + PTOL = 1e-12 + v = fp.e1(1.1641532182693481445e-10) + assert ae(v, 22.296641293693077672, tol=ATOL) + assert type(v) is float + v = fp.e1(0.25) + assert ae(v, 1.0442826344437381945, tol=ATOL) + assert type(v) is float + v = fp.e1(1.0) + assert ae(v, 0.21938393439552027368, tol=ATOL) + assert type(v) is float + v = fp.e1(2.0) + assert ae(v, 0.048900510708061119567, tol=ATOL) + assert type(v) is float + v = fp.e1(5.0) + assert ae(v, 0.0011482955912753257973, tol=ATOL) + assert type(v) is float + v = fp.e1(20.0) + assert ae(v, 9.8355252906498816904e-11, tol=ATOL) + assert type(v) is float + v = fp.e1(30.0) + assert ae(v, 3.0215520106888125448e-15, tol=ATOL) + assert type(v) is float + v = fp.e1(40.0) + assert ae(v, 1.0367732614516569722e-19, tol=ATOL) + assert type(v) is float + v = fp.e1(50.0) + assert ae(v, 3.7832640295504590187e-24, tol=ATOL) + assert type(v) is float + v = fp.e1(80.0) + assert ae(v, 2.2285432586884729112e-37, tol=ATOL) + assert type(v) is float + v = fp.e1((1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (22.296641293693077672 + 0.0j), tol=ATOL) + assert ae(v.real, 22.296641293693077672, tol=PTOL) + assert v.imag == 0 + v = fp.e1((0.25 + 0.0j)) + assert ae(v, (1.0442826344437381945 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0442826344437381945, tol=PTOL) + assert v.imag == 0 + v = fp.e1((1.0 + 0.0j)) + assert ae(v, (0.21938393439552027368 + 0.0j), tol=ATOL) + assert ae(v.real, 0.21938393439552027368, tol=PTOL) + assert v.imag == 0 + v = fp.e1((2.0 + 0.0j)) + assert ae(v, (0.048900510708061119567 + 0.0j), tol=ATOL) + assert ae(v.real, 0.048900510708061119567, tol=PTOL) + assert v.imag == 0 + v = fp.e1((5.0 + 0.0j)) + assert ae(v, (0.0011482955912753257973 + 0.0j), tol=ATOL) + assert ae(v.real, 0.0011482955912753257973, tol=PTOL) + assert v.imag == 0 + v = fp.e1((20.0 + 0.0j)) + assert ae(v, (9.8355252906498816904e-11 + 0.0j), tol=ATOL) + assert ae(v.real, 9.8355252906498816904e-11, tol=PTOL) + assert v.imag == 0 + v = fp.e1((30.0 + 0.0j)) + assert ae(v, (3.0215520106888125448e-15 + 0.0j), tol=ATOL) + assert ae(v.real, 3.0215520106888125448e-15, tol=PTOL) + assert v.imag == 0 + v = fp.e1((40.0 + 0.0j)) + assert ae(v, (1.0367732614516569722e-19 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0367732614516569722e-19, tol=PTOL) + assert v.imag == 0 + v = fp.e1((50.0 + 0.0j)) + assert ae(v, (3.7832640295504590187e-24 + 0.0j), tol=ATOL) + assert ae(v.real, 3.7832640295504590187e-24, tol=PTOL) + assert v.imag == 0 + v = fp.e1((80.0 + 0.0j)) + assert ae(v, (2.2285432586884729112e-37 + 0.0j), tol=ATOL) + assert ae(v.real, 2.2285432586884729112e-37, tol=PTOL) + assert v.imag == 0 + v = fp.e1((4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (20.880034622014215597 - 0.24497866301044883237j), tol=ATOL) + assert ae(v.real, 20.880034622014215597, tol=PTOL) + assert ae(v.imag, -0.24497866301044883237, tol=PTOL) + v = fp.e1((1.0 + 0.25j)) + assert ae(v, (0.19731063945004229095 - 0.087366045774299963672j), tol=ATOL) + assert ae(v.real, 0.19731063945004229095, tol=PTOL) + assert ae(v.imag, -0.087366045774299963672, tol=PTOL) + v = fp.e1((4.0 + 1.0j)) + assert ae(v, (0.0013106173980145506944 - 0.0034542480199350626699j), tol=ATOL) + assert ae(v.real, 0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, -0.0034542480199350626699, tol=PTOL) + v = fp.e1((8.0 + 2.0j)) + assert ae(v, (-0.000022278049065270225945 - 0.000029191940456521555288j), tol=ATOL) + assert ae(v.real, -0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, -0.000029191940456521555288, tol=PTOL) + v = fp.e1((20.0 + 5.0j)) + assert ae(v, (4.7711374515765346894e-11 + 8.2902652405126947359e-11j), tol=ATOL) + assert ae(v.real, 4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, 8.2902652405126947359e-11, tol=PTOL) + v = fp.e1((80.0 + 20.0j)) + assert ae(v, (3.8353473865788235787e-38 - 2.129247592349605139e-37j), tol=ATOL) + assert ae(v.real, 3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, -2.129247592349605139e-37, tol=PTOL) + v = fp.e1((120.0 + 30.0j)) + assert ae(v, (2.3836002337480334716e-55 + 5.6704043587126198306e-55j), tol=ATOL) + assert ae(v.real, 2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, 5.6704043587126198306e-55, tol=PTOL) + v = fp.e1((160.0 + 40.0j)) + assert ae(v, (-1.6238022898654510661e-72 - 1.104172355572287367e-72j), tol=ATOL) + assert ae(v.real, -1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, -1.104172355572287367e-72, tol=PTOL) + v = fp.e1((200.0 + 50.0j)) + assert ae(v, (6.6800061461666228487e-90 + 1.4473816083541016115e-91j), tol=ATOL) + assert ae(v.real, 6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, 1.4473816083541016115e-91, tol=PTOL) + v = fp.e1((320.0 + 80.0j)) + assert ae(v, (4.2737871527778786157e-143 + 3.1789935525785660314e-142j), tol=ATOL) + assert ae(v.real, 4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, 3.1789935525785660314e-142, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703413105017 - 0.7853981632810329878j), tol=ATOL) + assert ae(v.real, 21.950067703413105017, tol=PTOL) + assert ae(v.imag, -0.7853981632810329878, tol=PTOL) + v = fp.e1((0.25 + 0.25j)) + assert ae(v, (0.71092525792923287894 - 0.56491812441304194711j), tol=ATOL) + assert ae(v.real, 0.71092525792923287894, tol=PTOL) + assert ae(v.imag, -0.56491812441304194711, tol=PTOL) + v = fp.e1((1.0 + 1.0j)) + assert ae(v, (0.00028162445198141832551 - 0.17932453503935894015j), tol=ATOL) + assert ae(v.real, 0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, -0.17932453503935894015, tol=PTOL) + v = fp.e1((2.0 + 2.0j)) + assert ae(v, (-0.033767089606562004246 - 0.018599414169750541925j), tol=ATOL) + assert ae(v.real, -0.033767089606562004246, tol=PTOL) + assert ae(v.imag, -0.018599414169750541925, tol=PTOL) + v = fp.e1((5.0 + 5.0j)) + assert ae(v, (0.0007266506660356393891 + 0.00047102780163522245054j), tol=ATOL) + assert ae(v.real, 0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, 0.00047102780163522245054, tol=PTOL) + v = fp.e1((20.0 + 20.0j)) + assert ae(v, (-2.3824537449367396579e-11 - 6.6969873156525615158e-11j), tol=ATOL) + assert ae(v.real, -2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, -6.6969873156525615158e-11, tol=PTOL) + v = fp.e1((30.0 + 30.0j)) + assert ae(v, (1.7316045841744061617e-15 + 1.3065678019487308689e-15j), tol=ATOL) + assert ae(v.real, 1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, 1.3065678019487308689e-15, tol=PTOL) + v = fp.e1((40.0 + 40.0j)) + assert ae(v, (-7.4001043002899232182e-20 - 4.991847855336816304e-21j), tol=ATOL) + assert ae(v.real, -7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, -4.991847855336816304e-21, tol=PTOL) + v = fp.e1((50.0 + 50.0j)) + assert ae(v, (2.3566128324644641219e-24 - 1.3188326726201614778e-24j), tol=ATOL) + assert ae(v.real, 2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, -1.3188326726201614778e-24, tol=PTOL) + v = fp.e1((80.0 + 80.0j)) + assert ae(v, (9.8279750572186526673e-38 + 1.243952841288868831e-37j), tol=ATOL) + assert ae(v.real, 9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, 1.243952841288868831e-37, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621664969632 - 1.3258176632023711778j), tol=ATOL) + assert ae(v.real, 20.880034621664969632, tol=PTOL) + assert ae(v.imag, -1.3258176632023711778, tol=PTOL) + v = fp.e1((0.25 + 1.0j)) + assert ae(v, (-0.16868306393667788761 - 0.4858011885947426971j), tol=ATOL) + assert ae(v.real, -0.16868306393667788761, tol=PTOL) + assert ae(v.imag, -0.4858011885947426971, tol=PTOL) + v = fp.e1((1.0 + 4.0j)) + assert ae(v, (0.03373591813926547318 + 0.073523452241083821877j), tol=ATOL) + assert ae(v.real, 0.03373591813926547318, tol=PTOL) + assert ae(v.imag, 0.073523452241083821877, tol=PTOL) + v = fp.e1((2.0 + 8.0j)) + assert ae(v, (-0.015392833434733785143 - 0.0031747121557605415914j), tol=ATOL) + assert ae(v.real, -0.015392833434733785143, tol=PTOL) + assert ae(v.imag, -0.0031747121557605415914, tol=PTOL) + v = fp.e1((5.0 + 20.0j)) + assert ae(v, (-0.00024419662286542966525 - 0.00021008322966152755674j), tol=ATOL) + assert ae(v.real, -0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, -0.00021008322966152755674, tol=PTOL) + v = fp.e1((20.0 + 80.0j)) + assert ae(v, (2.3255552781051330088e-11 + 8.9463918891349438007e-12j), tol=ATOL) + assert ae(v.real, 2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, 8.9463918891349438007e-12, tol=PTOL) + v = fp.e1((30.0 + 120.0j)) + assert ae(v, (-2.7068919097124652332e-16 - 7.0477762411705130239e-16j), tol=ATOL) + assert ae(v.real, -2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, -7.0477762411705130239e-16, tol=PTOL) + v = fp.e1((40.0 + 160.0j)) + assert ae(v, (-1.1695597827678024687e-20 + 2.2907401455645736661e-20j), tol=ATOL) + assert ae(v.real, -1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, 2.2907401455645736661e-20, tol=PTOL) + v = fp.e1((50.0 + 200.0j)) + assert ae(v, (9.0323746914410162531e-25 - 2.3950601790033530935e-25j), tol=ATOL) + assert ae(v.real, 9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, -2.3950601790033530935e-25, tol=PTOL) + v = fp.e1((80.0 + 320.0j)) + assert ae(v, (3.4819106748728063576e-38 - 4.215653005615772724e-38j), tol=ATOL) + assert ae(v.real, 3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, -4.215653005615772724e-38, tol=PTOL) + v = fp.e1((0.0 + 1.1641532182693481445e-10j)) + assert ae(v, (22.29664129357666235 - 1.5707963266784812974j), tol=ATOL) + assert ae(v.real, 22.29664129357666235, tol=PTOL) + assert ae(v.imag, -1.5707963266784812974, tol=PTOL) + v = fp.e1((0.0 + 0.25j)) + assert ae(v, (0.82466306258094565309 - 1.3216627564751394551j), tol=ATOL) + assert ae(v.real, 0.82466306258094565309, tol=PTOL) + assert ae(v.imag, -1.3216627564751394551, tol=PTOL) + v = fp.e1((0.0 + 1.0j)) + assert ae(v, (-0.33740392290096813466 - 0.62471325642771360429j), tol=ATOL) + assert ae(v.real, -0.33740392290096813466, tol=PTOL) + assert ae(v.imag, -0.62471325642771360429, tol=PTOL) + v = fp.e1((0.0 + 2.0j)) + assert ae(v, (-0.4229808287748649957 + 0.034616650007798229345j), tol=ATOL) + assert ae(v.real, -0.4229808287748649957, tol=PTOL) + assert ae(v.imag, 0.034616650007798229345, tol=PTOL) + v = fp.e1((0.0 + 5.0j)) + assert ae(v, (0.19002974965664387862 - 0.020865081850222481957j), tol=ATOL) + assert ae(v.real, 0.19002974965664387862, tol=PTOL) + assert ae(v.imag, -0.020865081850222481957, tol=PTOL) + v = fp.e1((0.0 + 20.0j)) + assert ae(v, (-0.04441982084535331654 - 0.022554625751456779068j), tol=ATOL) + assert ae(v.real, -0.04441982084535331654, tol=PTOL) + assert ae(v.imag, -0.022554625751456779068, tol=PTOL) + v = fp.e1((0.0 + 30.0j)) + assert ae(v, (0.033032417282071143779 - 0.0040397867645455082476j), tol=ATOL) + assert ae(v.real, 0.033032417282071143779, tol=PTOL) + assert ae(v.imag, -0.0040397867645455082476, tol=PTOL) + v = fp.e1((0.0 + 40.0j)) + assert ae(v, (-0.019020007896208766962 + 0.016188792559887887544j), tol=ATOL) + assert ae(v.real, -0.019020007896208766962, tol=PTOL) + assert ae(v.imag, 0.016188792559887887544, tol=PTOL) + v = fp.e1((0.0 + 50.0j)) + assert ae(v, (0.0056283863241163054402 - 0.019179254308960724503j), tol=ATOL) + assert ae(v.real, 0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, -0.019179254308960724503, tol=PTOL) + v = fp.e1((0.0 + 80.0j)) + assert ae(v, (0.012402501155070958192 + 0.0015345601175906961199j), tol=ATOL) + assert ae(v.real, 0.012402501155070958192, tol=PTOL) + assert ae(v.imag, 0.0015345601175906961199, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621432138988 - 1.8157749894560994861j), tol=ATOL) + assert ae(v.real, 20.880034621432138988, tol=PTOL) + assert ae(v.imag, -1.8157749894560994861, tol=PTOL) + v = fp.e1((-0.25 + 1.0j)) + assert ae(v, (-0.59066621214766308594 - 0.74474454765205036972j), tol=ATOL) + assert ae(v.real, -0.59066621214766308594, tol=PTOL) + assert ae(v.imag, -0.74474454765205036972, tol=PTOL) + v = fp.e1((-1.0 + 4.0j)) + assert ae(v, (0.49739047283060471093 + 0.41543605404038863174j), tol=ATOL) + assert ae(v.real, 0.49739047283060471093, tol=PTOL) + assert ae(v.imag, 0.41543605404038863174, tol=PTOL) + v = fp.e1((-2.0 + 8.0j)) + assert ae(v, (-0.8705211147733730969 + 0.24099328498605539667j), tol=ATOL) + assert ae(v.real, -0.8705211147733730969, tol=PTOL) + assert ae(v.imag, 0.24099328498605539667, tol=PTOL) + v = fp.e1((-5.0 + 20.0j)) + assert ae(v, (-7.0789514293925893007 - 1.6102177171960790536j), tol=ATOL) + assert ae(v.real, -7.0789514293925893007, tol=PTOL) + assert ae(v.imag, -1.6102177171960790536, tol=PTOL) + v = fp.e1((-20.0 + 80.0j)) + assert ae(v, (5855431.4907298084434 - 720920.93315409165707j), tol=ATOL) + assert ae(v.real, 5855431.4907298084434, tol=PTOL) + assert ae(v.imag, -720920.93315409165707, tol=PTOL) + v = fp.e1((-30.0 + 120.0j)) + assert ae(v, (-65402491644.703470747 - 56697658399.657460294j), tol=ATOL) + assert ae(v.real, -65402491644.703470747, tol=PTOL) + assert ae(v.imag, -56697658399.657460294, tol=PTOL) + v = fp.e1((-40.0 + 160.0j)) + assert ae(v, (25504929379604.776769 + 1429035198630573.2463j), tol=ATOL) + assert ae(v.real, 25504929379604.776769, tol=PTOL) + assert ae(v.imag, 1429035198630573.2463, tol=PTOL) + v = fp.e1((-50.0 + 200.0j)) + assert ae(v, (18437746526988116954.0 - 17146362239046152345.0j), tol=ATOL) + assert ae(v.real, 18437746526988116954.0, tol=PTOL) + assert ae(v.imag, -17146362239046152345.0, tol=PTOL) + v = fp.e1((-80.0 + 320.0j)) + assert ae(v, (3.3464697299634526706e+31 - 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, 3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, -1.6473152633843023919e+32, tol=PTOL) + v = fp.e1((-4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (20.880034621082893023 - 2.8966139903465137624j), tol=ATOL) + assert ae(v.real, 20.880034621082893023, tol=PTOL) + assert ae(v.imag, -2.8966139903465137624, tol=PTOL) + v = fp.e1((-1.0 + 0.25j)) + assert ae(v, (-1.8942716983721074932 - 2.4689102827070540799j), tol=ATOL) + assert ae(v.real, -1.8942716983721074932, tol=PTOL) + assert ae(v.imag, -2.4689102827070540799, tol=PTOL) + v = fp.e1((-4.0 + 1.0j)) + assert ae(v, (-14.806699492675420438 + 9.1384225230837893776j), tol=ATOL) + assert ae(v.real, -14.806699492675420438, tol=PTOL) + assert ae(v.imag, 9.1384225230837893776, tol=PTOL) + v = fp.e1((-8.0 + 2.0j)) + assert ae(v, (54.633252667426386294 + 413.20318163814670688j), tol=ATOL) + assert ae(v.real, 54.633252667426386294, tol=PTOL) + assert ae(v.imag, 413.20318163814670688, tol=PTOL) + v = fp.e1((-20.0 + 5.0j)) + assert ae(v, (-711836.97165402624643 - 24745250.939695900956j), tol=ATOL) + assert ae(v.real, -711836.97165402624643, tol=PTOL) + assert ae(v.imag, -24745250.939695900956, tol=PTOL) + v = fp.e1((-80.0 + 20.0j)) + assert ae(v, (-4.2139911108612653091e+32 + 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, -4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, 5.3367124741918251637e+32, tol=PTOL) + v = fp.e1((-120.0 + 30.0j)) + assert ae(v, (9.7760616203707508892e+48 - 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, 9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, -1.058257682317195792e+50, tol=PTOL) + v = fp.e1((-160.0 + 40.0j)) + assert ae(v, (8.7065541466623638861e+66 + 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, 8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, 1.6577106725141739889e+67, tol=PTOL) + v = fp.e1((-200.0 + 50.0j)) + assert ae(v, (-3.070744996327018106e+84 - 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, -3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, -1.7243244846769415903e+84, tol=PTOL) + v = fp.e1((-320.0 + 80.0j)) + assert ae(v, (9.9960598637998647276e+135 - 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, 9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, -2.6855081527595608863e+136, tol=PTOL) + v = fp.e1(-1.1641532182693481445e-10) + assert ae(v, (22.296641293460247028 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 22.296641293460247028, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-0.25) + assert ae(v, (0.54254326466191372953 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 0.54254326466191372953, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-1.0) + assert ae(v, (-1.8951178163559367555 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.8951178163559367555, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-2.0) + assert ae(v, (-4.9542343560018901634 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.9542343560018901634, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-5.0) + assert ae(v, (-40.185275355803177455 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -40.185275355803177455, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-20.0) + assert ae(v, (-25615652.66405658882 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -25615652.66405658882, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-30.0) + assert ae(v, (-368973209407.27419706 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -368973209407.27419706, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-40.0) + assert ae(v, (-6039718263611241.5784 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6039718263611241.5784, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-50.0) + assert ae(v, (-1.0585636897131690963e+20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.0585636897131690963e+20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1(-80.0) + assert ae(v, (-7.0146000049047999696e+32 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -7.0146000049047999696e+32, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (22.296641293460247028 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 22.296641293460247028, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-0.25 + 0.0j)) + assert ae(v, (0.54254326466191372953 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 0.54254326466191372953, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-1.0 + 0.0j)) + assert ae(v, (-1.8951178163559367555 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.8951178163559367555, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-2.0 + 0.0j)) + assert ae(v, (-4.9542343560018901634 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.9542343560018901634, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-5.0 + 0.0j)) + assert ae(v, (-40.185275355803177455 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -40.185275355803177455, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-20.0 + 0.0j)) + assert ae(v, (-25615652.66405658882 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -25615652.66405658882, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-30.0 + 0.0j)) + assert ae(v, (-368973209407.27419706 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -368973209407.27419706, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-40.0 + 0.0j)) + assert ae(v, (-6039718263611241.5784 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6039718263611241.5784, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-50.0 + 0.0j)) + assert ae(v, (-1.0585636897131690963e+20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -1.0585636897131690963e+20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-80.0 + 0.0j)) + assert ae(v, (-7.0146000049047999696e+32 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -7.0146000049047999696e+32, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.e1((-4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (20.880034621082893023 + 2.8966139903465137624j), tol=ATOL) + assert ae(v.real, 20.880034621082893023, tol=PTOL) + assert ae(v.imag, 2.8966139903465137624, tol=PTOL) + v = fp.e1((-1.0 - 0.25j)) + assert ae(v, (-1.8942716983721074932 + 2.4689102827070540799j), tol=ATOL) + assert ae(v.real, -1.8942716983721074932, tol=PTOL) + assert ae(v.imag, 2.4689102827070540799, tol=PTOL) + v = fp.e1((-4.0 - 1.0j)) + assert ae(v, (-14.806699492675420438 - 9.1384225230837893776j), tol=ATOL) + assert ae(v.real, -14.806699492675420438, tol=PTOL) + assert ae(v.imag, -9.1384225230837893776, tol=PTOL) + v = fp.e1((-8.0 - 2.0j)) + assert ae(v, (54.633252667426386294 - 413.20318163814670688j), tol=ATOL) + assert ae(v.real, 54.633252667426386294, tol=PTOL) + assert ae(v.imag, -413.20318163814670688, tol=PTOL) + v = fp.e1((-20.0 - 5.0j)) + assert ae(v, (-711836.97165402624643 + 24745250.939695900956j), tol=ATOL) + assert ae(v.real, -711836.97165402624643, tol=PTOL) + assert ae(v.imag, 24745250.939695900956, tol=PTOL) + v = fp.e1((-80.0 - 20.0j)) + assert ae(v, (-4.2139911108612653091e+32 - 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, -4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, -5.3367124741918251637e+32, tol=PTOL) + v = fp.e1((-120.0 - 30.0j)) + assert ae(v, (9.7760616203707508892e+48 + 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, 9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, 1.058257682317195792e+50, tol=PTOL) + v = fp.e1((-160.0 - 40.0j)) + assert ae(v, (8.7065541466623638861e+66 - 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, 8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, -1.6577106725141739889e+67, tol=PTOL) + v = fp.e1((-200.0 - 50.0j)) + assert ae(v, (-3.070744996327018106e+84 + 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, -3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, 1.7243244846769415903e+84, tol=PTOL) + v = fp.e1((-320.0 - 80.0j)) + assert ae(v, (9.9960598637998647276e+135 + 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, 9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, 2.6855081527595608863e+136, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703180274374 + 2.356194490075929607j), tol=ATOL) + assert ae(v.real, 21.950067703180274374, tol=PTOL) + assert ae(v.imag, 2.356194490075929607, tol=PTOL) + v = fp.e1((-0.25 - 0.25j)) + assert ae(v, (0.21441047326710323254 + 2.0732153554307936389j), tol=ATOL) + assert ae(v.real, 0.21441047326710323254, tol=PTOL) + assert ae(v.imag, 2.0732153554307936389, tol=PTOL) + v = fp.e1((-1.0 - 1.0j)) + assert ae(v, (-1.7646259855638540684 + 0.7538228020792708192j), tol=ATOL) + assert ae(v.real, -1.7646259855638540684, tol=PTOL) + assert ae(v.imag, 0.7538228020792708192, tol=PTOL) + v = fp.e1((-2.0 - 2.0j)) + assert ae(v, (-1.8920781621855474089 - 2.1753697842428647236j), tol=ATOL) + assert ae(v.real, -1.8920781621855474089, tol=PTOL) + assert ae(v.imag, -2.1753697842428647236, tol=PTOL) + v = fp.e1((-5.0 - 5.0j)) + assert ae(v, (13.470936071475245856 + 18.464085049321024206j), tol=ATOL) + assert ae(v.real, 13.470936071475245856, tol=PTOL) + assert ae(v.imag, 18.464085049321024206, tol=PTOL) + v = fp.e1((-20.0 - 20.0j)) + assert ae(v, (-16589317.398788971896 - 5831702.3296441771206j), tol=ATOL) + assert ae(v.real, -16589317.398788971896, tol=PTOL) + assert ae(v.imag, -5831702.3296441771206, tol=PTOL) + v = fp.e1((-30.0 - 30.0j)) + assert ae(v, (154596484273.69322527 + 204179357837.41389696j), tol=ATOL) + assert ae(v.real, 154596484273.69322527, tol=PTOL) + assert ae(v.imag, 204179357837.41389696, tol=PTOL) + v = fp.e1((-40.0 - 40.0j)) + assert ae(v, (-287512180321448.45408 - 4203502407932314.974j), tol=ATOL) + assert ae(v.real, -287512180321448.45408, tol=PTOL) + assert ae(v.imag, -4203502407932314.974, tol=PTOL) + v = fp.e1((-50.0 - 50.0j)) + assert ae(v, (-36128528616649268826.0 + 64648801861338741963.0j), tol=ATOL) + assert ae(v.real, -36128528616649268826.0, tol=PTOL) + assert ae(v.imag, 64648801861338741963.0, tol=PTOL) + v = fp.e1((-80.0 - 80.0j)) + assert ae(v, (3.8674816337930010217e+32 + 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, 3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, 3.0540709639658071041e+32, tol=PTOL) + v = fp.e1((-1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621432138988 + 1.8157749894560994861j), tol=ATOL) + assert ae(v.real, 20.880034621432138988, tol=PTOL) + assert ae(v.imag, 1.8157749894560994861, tol=PTOL) + v = fp.e1((-0.25 - 1.0j)) + assert ae(v, (-0.59066621214766308594 + 0.74474454765205036972j), tol=ATOL) + assert ae(v.real, -0.59066621214766308594, tol=PTOL) + assert ae(v.imag, 0.74474454765205036972, tol=PTOL) + v = fp.e1((-1.0 - 4.0j)) + assert ae(v, (0.49739047283060471093 - 0.41543605404038863174j), tol=ATOL) + assert ae(v.real, 0.49739047283060471093, tol=PTOL) + assert ae(v.imag, -0.41543605404038863174, tol=PTOL) + v = fp.e1((-2.0 - 8.0j)) + assert ae(v, (-0.8705211147733730969 - 0.24099328498605539667j), tol=ATOL) + assert ae(v.real, -0.8705211147733730969, tol=PTOL) + assert ae(v.imag, -0.24099328498605539667, tol=PTOL) + v = fp.e1((-5.0 - 20.0j)) + assert ae(v, (-7.0789514293925893007 + 1.6102177171960790536j), tol=ATOL) + assert ae(v.real, -7.0789514293925893007, tol=PTOL) + assert ae(v.imag, 1.6102177171960790536, tol=PTOL) + v = fp.e1((-20.0 - 80.0j)) + assert ae(v, (5855431.4907298084434 + 720920.93315409165707j), tol=ATOL) + assert ae(v.real, 5855431.4907298084434, tol=PTOL) + assert ae(v.imag, 720920.93315409165707, tol=PTOL) + v = fp.e1((-30.0 - 120.0j)) + assert ae(v, (-65402491644.703470747 + 56697658399.657460294j), tol=ATOL) + assert ae(v.real, -65402491644.703470747, tol=PTOL) + assert ae(v.imag, 56697658399.657460294, tol=PTOL) + v = fp.e1((-40.0 - 160.0j)) + assert ae(v, (25504929379604.776769 - 1429035198630573.2463j), tol=ATOL) + assert ae(v.real, 25504929379604.776769, tol=PTOL) + assert ae(v.imag, -1429035198630573.2463, tol=PTOL) + v = fp.e1((-50.0 - 200.0j)) + assert ae(v, (18437746526988116954.0 + 17146362239046152345.0j), tol=ATOL) + assert ae(v.real, 18437746526988116954.0, tol=PTOL) + assert ae(v.imag, 17146362239046152345.0, tol=PTOL) + v = fp.e1((-80.0 - 320.0j)) + assert ae(v, (3.3464697299634526706e+31 + 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, 3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, 1.6473152633843023919e+32, tol=PTOL) + v = fp.e1((0.0 - 1.1641532182693481445e-10j)) + assert ae(v, (22.29664129357666235 + 1.5707963266784812974j), tol=ATOL) + assert ae(v.real, 22.29664129357666235, tol=PTOL) + assert ae(v.imag, 1.5707963266784812974, tol=PTOL) + v = fp.e1((0.0 - 0.25j)) + assert ae(v, (0.82466306258094565309 + 1.3216627564751394551j), tol=ATOL) + assert ae(v.real, 0.82466306258094565309, tol=PTOL) + assert ae(v.imag, 1.3216627564751394551, tol=PTOL) + v = fp.e1((0.0 - 1.0j)) + assert ae(v, (-0.33740392290096813466 + 0.62471325642771360429j), tol=ATOL) + assert ae(v.real, -0.33740392290096813466, tol=PTOL) + assert ae(v.imag, 0.62471325642771360429, tol=PTOL) + v = fp.e1((0.0 - 2.0j)) + assert ae(v, (-0.4229808287748649957 - 0.034616650007798229345j), tol=ATOL) + assert ae(v.real, -0.4229808287748649957, tol=PTOL) + assert ae(v.imag, -0.034616650007798229345, tol=PTOL) + v = fp.e1((0.0 - 5.0j)) + assert ae(v, (0.19002974965664387862 + 0.020865081850222481957j), tol=ATOL) + assert ae(v.real, 0.19002974965664387862, tol=PTOL) + assert ae(v.imag, 0.020865081850222481957, tol=PTOL) + v = fp.e1((0.0 - 20.0j)) + assert ae(v, (-0.04441982084535331654 + 0.022554625751456779068j), tol=ATOL) + assert ae(v.real, -0.04441982084535331654, tol=PTOL) + assert ae(v.imag, 0.022554625751456779068, tol=PTOL) + v = fp.e1((0.0 - 30.0j)) + assert ae(v, (0.033032417282071143779 + 0.0040397867645455082476j), tol=ATOL) + assert ae(v.real, 0.033032417282071143779, tol=PTOL) + assert ae(v.imag, 0.0040397867645455082476, tol=PTOL) + v = fp.e1((0.0 - 40.0j)) + assert ae(v, (-0.019020007896208766962 - 0.016188792559887887544j), tol=ATOL) + assert ae(v.real, -0.019020007896208766962, tol=PTOL) + assert ae(v.imag, -0.016188792559887887544, tol=PTOL) + v = fp.e1((0.0 - 50.0j)) + assert ae(v, (0.0056283863241163054402 + 0.019179254308960724503j), tol=ATOL) + assert ae(v.real, 0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, 0.019179254308960724503, tol=PTOL) + v = fp.e1((0.0 - 80.0j)) + assert ae(v, (0.012402501155070958192 - 0.0015345601175906961199j), tol=ATOL) + assert ae(v.real, 0.012402501155070958192, tol=PTOL) + assert ae(v.imag, -0.0015345601175906961199, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (20.880034621664969632 + 1.3258176632023711778j), tol=ATOL) + assert ae(v.real, 20.880034621664969632, tol=PTOL) + assert ae(v.imag, 1.3258176632023711778, tol=PTOL) + v = fp.e1((0.25 - 1.0j)) + assert ae(v, (-0.16868306393667788761 + 0.4858011885947426971j), tol=ATOL) + assert ae(v.real, -0.16868306393667788761, tol=PTOL) + assert ae(v.imag, 0.4858011885947426971, tol=PTOL) + v = fp.e1((1.0 - 4.0j)) + assert ae(v, (0.03373591813926547318 - 0.073523452241083821877j), tol=ATOL) + assert ae(v.real, 0.03373591813926547318, tol=PTOL) + assert ae(v.imag, -0.073523452241083821877, tol=PTOL) + v = fp.e1((2.0 - 8.0j)) + assert ae(v, (-0.015392833434733785143 + 0.0031747121557605415914j), tol=ATOL) + assert ae(v.real, -0.015392833434733785143, tol=PTOL) + assert ae(v.imag, 0.0031747121557605415914, tol=PTOL) + v = fp.e1((5.0 - 20.0j)) + assert ae(v, (-0.00024419662286542966525 + 0.00021008322966152755674j), tol=ATOL) + assert ae(v.real, -0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, 0.00021008322966152755674, tol=PTOL) + v = fp.e1((20.0 - 80.0j)) + assert ae(v, (2.3255552781051330088e-11 - 8.9463918891349438007e-12j), tol=ATOL) + assert ae(v.real, 2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, -8.9463918891349438007e-12, tol=PTOL) + v = fp.e1((30.0 - 120.0j)) + assert ae(v, (-2.7068919097124652332e-16 + 7.0477762411705130239e-16j), tol=ATOL) + assert ae(v.real, -2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, 7.0477762411705130239e-16, tol=PTOL) + v = fp.e1((40.0 - 160.0j)) + assert ae(v, (-1.1695597827678024687e-20 - 2.2907401455645736661e-20j), tol=ATOL) + assert ae(v.real, -1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, -2.2907401455645736661e-20, tol=PTOL) + v = fp.e1((50.0 - 200.0j)) + assert ae(v, (9.0323746914410162531e-25 + 2.3950601790033530935e-25j), tol=ATOL) + assert ae(v.real, 9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, 2.3950601790033530935e-25, tol=PTOL) + v = fp.e1((80.0 - 320.0j)) + assert ae(v, (3.4819106748728063576e-38 + 4.215653005615772724e-38j), tol=ATOL) + assert ae(v.real, 3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, 4.215653005615772724e-38, tol=PTOL) + v = fp.e1((1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (21.950067703413105017 + 0.7853981632810329878j), tol=ATOL) + assert ae(v.real, 21.950067703413105017, tol=PTOL) + assert ae(v.imag, 0.7853981632810329878, tol=PTOL) + v = fp.e1((0.25 - 0.25j)) + assert ae(v, (0.71092525792923287894 + 0.56491812441304194711j), tol=ATOL) + assert ae(v.real, 0.71092525792923287894, tol=PTOL) + assert ae(v.imag, 0.56491812441304194711, tol=PTOL) + v = fp.e1((1.0 - 1.0j)) + assert ae(v, (0.00028162445198141832551 + 0.17932453503935894015j), tol=ATOL) + assert ae(v.real, 0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, 0.17932453503935894015, tol=PTOL) + v = fp.e1((2.0 - 2.0j)) + assert ae(v, (-0.033767089606562004246 + 0.018599414169750541925j), tol=ATOL) + assert ae(v.real, -0.033767089606562004246, tol=PTOL) + assert ae(v.imag, 0.018599414169750541925, tol=PTOL) + v = fp.e1((5.0 - 5.0j)) + assert ae(v, (0.0007266506660356393891 - 0.00047102780163522245054j), tol=ATOL) + assert ae(v.real, 0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, -0.00047102780163522245054, tol=PTOL) + v = fp.e1((20.0 - 20.0j)) + assert ae(v, (-2.3824537449367396579e-11 + 6.6969873156525615158e-11j), tol=ATOL) + assert ae(v.real, -2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, 6.6969873156525615158e-11, tol=PTOL) + v = fp.e1((30.0 - 30.0j)) + assert ae(v, (1.7316045841744061617e-15 - 1.3065678019487308689e-15j), tol=ATOL) + assert ae(v.real, 1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, -1.3065678019487308689e-15, tol=PTOL) + v = fp.e1((40.0 - 40.0j)) + assert ae(v, (-7.4001043002899232182e-20 + 4.991847855336816304e-21j), tol=ATOL) + assert ae(v.real, -7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, 4.991847855336816304e-21, tol=PTOL) + v = fp.e1((50.0 - 50.0j)) + assert ae(v, (2.3566128324644641219e-24 + 1.3188326726201614778e-24j), tol=ATOL) + assert ae(v.real, 2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, 1.3188326726201614778e-24, tol=PTOL) + v = fp.e1((80.0 - 80.0j)) + assert ae(v, (9.8279750572186526673e-38 - 1.243952841288868831e-37j), tol=ATOL) + assert ae(v.real, 9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, -1.243952841288868831e-37, tol=PTOL) + v = fp.e1((4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (20.880034622014215597 + 0.24497866301044883237j), tol=ATOL) + assert ae(v.real, 20.880034622014215597, tol=PTOL) + assert ae(v.imag, 0.24497866301044883237, tol=PTOL) + v = fp.e1((1.0 - 0.25j)) + assert ae(v, (0.19731063945004229095 + 0.087366045774299963672j), tol=ATOL) + assert ae(v.real, 0.19731063945004229095, tol=PTOL) + assert ae(v.imag, 0.087366045774299963672, tol=PTOL) + v = fp.e1((4.0 - 1.0j)) + assert ae(v, (0.0013106173980145506944 + 0.0034542480199350626699j), tol=ATOL) + assert ae(v.real, 0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, 0.0034542480199350626699, tol=PTOL) + v = fp.e1((8.0 - 2.0j)) + assert ae(v, (-0.000022278049065270225945 + 0.000029191940456521555288j), tol=ATOL) + assert ae(v.real, -0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, 0.000029191940456521555288, tol=PTOL) + v = fp.e1((20.0 - 5.0j)) + assert ae(v, (4.7711374515765346894e-11 - 8.2902652405126947359e-11j), tol=ATOL) + assert ae(v.real, 4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, -8.2902652405126947359e-11, tol=PTOL) + v = fp.e1((80.0 - 20.0j)) + assert ae(v, (3.8353473865788235787e-38 + 2.129247592349605139e-37j), tol=ATOL) + assert ae(v.real, 3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, 2.129247592349605139e-37, tol=PTOL) + v = fp.e1((120.0 - 30.0j)) + assert ae(v, (2.3836002337480334716e-55 - 5.6704043587126198306e-55j), tol=ATOL) + assert ae(v.real, 2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, -5.6704043587126198306e-55, tol=PTOL) + v = fp.e1((160.0 - 40.0j)) + assert ae(v, (-1.6238022898654510661e-72 + 1.104172355572287367e-72j), tol=ATOL) + assert ae(v.real, -1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, 1.104172355572287367e-72, tol=PTOL) + v = fp.e1((200.0 - 50.0j)) + assert ae(v, (6.6800061461666228487e-90 - 1.4473816083541016115e-91j), tol=ATOL) + assert ae(v.real, 6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, -1.4473816083541016115e-91, tol=PTOL) + v = fp.e1((320.0 - 80.0j)) + assert ae(v, (4.2737871527778786157e-143 - 3.1789935525785660314e-142j), tol=ATOL) + assert ae(v.real, 4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, -3.1789935525785660314e-142, tol=PTOL) + v = fp.ei(1.1641532182693481445e-10) + assert ae(v, -22.296641293460247028, tol=ATOL) + assert type(v) is float + v = fp.ei(0.25) + assert ae(v, -0.54254326466191372953, tol=ATOL) + assert type(v) is float + v = fp.ei(1.0) + assert ae(v, 1.8951178163559367555, tol=ATOL) + assert type(v) is float + v = fp.ei(2.0) + assert ae(v, 4.9542343560018901634, tol=ATOL) + assert type(v) is float + v = fp.ei(5.0) + assert ae(v, 40.185275355803177455, tol=ATOL) + assert type(v) is float + v = fp.ei(20.0) + assert ae(v, 25615652.66405658882, tol=ATOL) + assert type(v) is float + v = fp.ei(30.0) + assert ae(v, 368973209407.27419706, tol=ATOL) + assert type(v) is float + v = fp.ei(40.0) + assert ae(v, 6039718263611241.5784, tol=ATOL) + assert type(v) is float + v = fp.ei(50.0) + assert ae(v, 1.0585636897131690963e+20, tol=ATOL) + assert type(v) is float + v = fp.ei(80.0) + assert ae(v, 7.0146000049047999696e+32, tol=ATOL) + assert type(v) is float + v = fp.ei((1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (-22.296641293460247028 + 0.0j), tol=ATOL) + assert ae(v.real, -22.296641293460247028, tol=PTOL) + assert v.imag == 0 + v = fp.ei((0.25 + 0.0j)) + assert ae(v, (-0.54254326466191372953 + 0.0j), tol=ATOL) + assert ae(v.real, -0.54254326466191372953, tol=PTOL) + assert v.imag == 0 + v = fp.ei((1.0 + 0.0j)) + assert ae(v, (1.8951178163559367555 + 0.0j), tol=ATOL) + assert ae(v.real, 1.8951178163559367555, tol=PTOL) + assert v.imag == 0 + v = fp.ei((2.0 + 0.0j)) + assert ae(v, (4.9542343560018901634 + 0.0j), tol=ATOL) + assert ae(v.real, 4.9542343560018901634, tol=PTOL) + assert v.imag == 0 + v = fp.ei((5.0 + 0.0j)) + assert ae(v, (40.185275355803177455 + 0.0j), tol=ATOL) + assert ae(v.real, 40.185275355803177455, tol=PTOL) + assert v.imag == 0 + v = fp.ei((20.0 + 0.0j)) + assert ae(v, (25615652.66405658882 + 0.0j), tol=ATOL) + assert ae(v.real, 25615652.66405658882, tol=PTOL) + assert v.imag == 0 + v = fp.ei((30.0 + 0.0j)) + assert ae(v, (368973209407.27419706 + 0.0j), tol=ATOL) + assert ae(v.real, 368973209407.27419706, tol=PTOL) + assert v.imag == 0 + v = fp.ei((40.0 + 0.0j)) + assert ae(v, (6039718263611241.5784 + 0.0j), tol=ATOL) + assert ae(v.real, 6039718263611241.5784, tol=PTOL) + assert v.imag == 0 + v = fp.ei((50.0 + 0.0j)) + assert ae(v, (1.0585636897131690963e+20 + 0.0j), tol=ATOL) + assert ae(v.real, 1.0585636897131690963e+20, tol=PTOL) + assert v.imag == 0 + v = fp.ei((80.0 + 0.0j)) + assert ae(v, (7.0146000049047999696e+32 + 0.0j), tol=ATOL) + assert ae(v.real, 7.0146000049047999696e+32, tol=PTOL) + assert v.imag == 0 + v = fp.ei((4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034621082893023 + 0.24497866324327947603j), tol=ATOL) + assert ae(v.real, -20.880034621082893023, tol=PTOL) + assert ae(v.imag, 0.24497866324327947603, tol=PTOL) + v = fp.ei((1.0 + 0.25j)) + assert ae(v, (1.8942716983721074932 + 0.67268237088273915854j), tol=ATOL) + assert ae(v.real, 1.8942716983721074932, tol=PTOL) + assert ae(v.imag, 0.67268237088273915854, tol=PTOL) + v = fp.ei((4.0 + 1.0j)) + assert ae(v, (14.806699492675420438 + 12.280015176673582616j), tol=ATOL) + assert ae(v.real, 14.806699492675420438, tol=PTOL) + assert ae(v.imag, 12.280015176673582616, tol=PTOL) + v = fp.ei((8.0 + 2.0j)) + assert ae(v, (-54.633252667426386294 + 416.34477429173650012j), tol=ATOL) + assert ae(v.real, -54.633252667426386294, tol=PTOL) + assert ae(v.imag, 416.34477429173650012, tol=PTOL) + v = fp.ei((20.0 + 5.0j)) + assert ae(v, (711836.97165402624643 - 24745247.798103247366j), tol=ATOL) + assert ae(v.real, 711836.97165402624643, tol=PTOL) + assert ae(v.imag, -24745247.798103247366, tol=PTOL) + v = fp.ei((80.0 + 20.0j)) + assert ae(v, (4.2139911108612653091e+32 + 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, 4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, 5.3367124741918251637e+32, tol=PTOL) + v = fp.ei((120.0 + 30.0j)) + assert ae(v, (-9.7760616203707508892e+48 - 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, -9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, -1.058257682317195792e+50, tol=PTOL) + v = fp.ei((160.0 + 40.0j)) + assert ae(v, (-8.7065541466623638861e+66 + 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, -8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, 1.6577106725141739889e+67, tol=PTOL) + v = fp.ei((200.0 + 50.0j)) + assert ae(v, (3.070744996327018106e+84 - 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, 3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, -1.7243244846769415903e+84, tol=PTOL) + v = fp.ei((320.0 + 80.0j)) + assert ae(v, (-9.9960598637998647276e+135 - 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, -9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, -2.6855081527595608863e+136, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703180274374 + 0.78539816351386363145j), tol=ATOL) + assert ae(v.real, -21.950067703180274374, tol=PTOL) + assert ae(v.imag, 0.78539816351386363145, tol=PTOL) + v = fp.ei((0.25 + 0.25j)) + assert ae(v, (-0.21441047326710323254 + 1.0683772981589995996j), tol=ATOL) + assert ae(v.real, -0.21441047326710323254, tol=PTOL) + assert ae(v.imag, 1.0683772981589995996, tol=PTOL) + v = fp.ei((1.0 + 1.0j)) + assert ae(v, (1.7646259855638540684 + 2.3877698515105224193j), tol=ATOL) + assert ae(v.real, 1.7646259855638540684, tol=PTOL) + assert ae(v.imag, 2.3877698515105224193, tol=PTOL) + v = fp.ei((2.0 + 2.0j)) + assert ae(v, (1.8920781621855474089 + 5.3169624378326579621j), tol=ATOL) + assert ae(v.real, 1.8920781621855474089, tol=PTOL) + assert ae(v.imag, 5.3169624378326579621, tol=PTOL) + v = fp.ei((5.0 + 5.0j)) + assert ae(v, (-13.470936071475245856 - 15.322492395731230968j), tol=ATOL) + assert ae(v.real, -13.470936071475245856, tol=PTOL) + assert ae(v.imag, -15.322492395731230968, tol=PTOL) + v = fp.ei((20.0 + 20.0j)) + assert ae(v, (16589317.398788971896 + 5831705.4712368307104j), tol=ATOL) + assert ae(v.real, 16589317.398788971896, tol=PTOL) + assert ae(v.imag, 5831705.4712368307104, tol=PTOL) + v = fp.ei((30.0 + 30.0j)) + assert ae(v, (-154596484273.69322527 - 204179357834.2723043j), tol=ATOL) + assert ae(v.real, -154596484273.69322527, tol=PTOL) + assert ae(v.imag, -204179357834.2723043, tol=PTOL) + v = fp.ei((40.0 + 40.0j)) + assert ae(v, (287512180321448.45408 + 4203502407932318.1156j), tol=ATOL) + assert ae(v.real, 287512180321448.45408, tol=PTOL) + assert ae(v.imag, 4203502407932318.1156, tol=PTOL) + v = fp.ei((50.0 + 50.0j)) + assert ae(v, (36128528616649268826.0 - 64648801861338741960.0j), tol=ATOL) + assert ae(v.real, 36128528616649268826.0, tol=PTOL) + assert ae(v.imag, -64648801861338741960.0, tol=PTOL) + v = fp.ei((80.0 + 80.0j)) + assert ae(v, (-3.8674816337930010217e+32 - 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, -3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, -3.0540709639658071041e+32, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621432138988 + 1.3258176641336937524j), tol=ATOL) + assert ae(v.real, -20.880034621432138988, tol=PTOL) + assert ae(v.imag, 1.3258176641336937524, tol=PTOL) + v = fp.ei((0.25 + 1.0j)) + assert ae(v, (0.59066621214766308594 + 2.3968481059377428687j), tol=ATOL) + assert ae(v.real, 0.59066621214766308594, tol=PTOL) + assert ae(v.imag, 2.3968481059377428687, tol=PTOL) + v = fp.ei((1.0 + 4.0j)) + assert ae(v, (-0.49739047283060471093 + 3.5570287076301818702j), tol=ATOL) + assert ae(v.real, -0.49739047283060471093, tol=PTOL) + assert ae(v.imag, 3.5570287076301818702, tol=PTOL) + v = fp.ei((2.0 + 8.0j)) + assert ae(v, (0.8705211147733730969 + 3.3825859385758486351j), tol=ATOL) + assert ae(v.real, 0.8705211147733730969, tol=PTOL) + assert ae(v.imag, 3.3825859385758486351, tol=PTOL) + v = fp.ei((5.0 + 20.0j)) + assert ae(v, (7.0789514293925893007 + 1.5313749363937141849j), tol=ATOL) + assert ae(v.real, 7.0789514293925893007, tol=PTOL) + assert ae(v.imag, 1.5313749363937141849, tol=PTOL) + v = fp.ei((20.0 + 80.0j)) + assert ae(v, (-5855431.4907298084434 - 720917.79156143806727j), tol=ATOL) + assert ae(v.real, -5855431.4907298084434, tol=PTOL) + assert ae(v.imag, -720917.79156143806727, tol=PTOL) + v = fp.ei((30.0 + 120.0j)) + assert ae(v, (65402491644.703470747 - 56697658396.51586764j), tol=ATOL) + assert ae(v.real, 65402491644.703470747, tol=PTOL) + assert ae(v.imag, -56697658396.51586764, tol=PTOL) + v = fp.ei((40.0 + 160.0j)) + assert ae(v, (-25504929379604.776769 + 1429035198630576.3879j), tol=ATOL) + assert ae(v.real, -25504929379604.776769, tol=PTOL) + assert ae(v.imag, 1429035198630576.3879, tol=PTOL) + v = fp.ei((50.0 + 200.0j)) + assert ae(v, (-18437746526988116954.0 - 17146362239046152342.0j), tol=ATOL) + assert ae(v.real, -18437746526988116954.0, tol=PTOL) + assert ae(v.imag, -17146362239046152342.0, tol=PTOL) + v = fp.ei((80.0 + 320.0j)) + assert ae(v, (-3.3464697299634526706e+31 - 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, -3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, -1.6473152633843023919e+32, tol=PTOL) + v = fp.ei((0.0 + 1.1641532182693481445e-10j)) + assert ae(v, (-22.29664129357666235 + 1.5707963269113119411j), tol=ATOL) + assert ae(v.real, -22.29664129357666235, tol=PTOL) + assert ae(v.imag, 1.5707963269113119411, tol=PTOL) + v = fp.ei((0.0 + 0.25j)) + assert ae(v, (-0.82466306258094565309 + 1.8199298971146537833j), tol=ATOL) + assert ae(v.real, -0.82466306258094565309, tol=PTOL) + assert ae(v.imag, 1.8199298971146537833, tol=PTOL) + v = fp.ei((0.0 + 1.0j)) + assert ae(v, (0.33740392290096813466 + 2.5168793971620796342j), tol=ATOL) + assert ae(v.real, 0.33740392290096813466, tol=PTOL) + assert ae(v.imag, 2.5168793971620796342, tol=PTOL) + v = fp.ei((0.0 + 2.0j)) + assert ae(v, (0.4229808287748649957 + 3.1762093035975914678j), tol=ATOL) + assert ae(v.real, 0.4229808287748649957, tol=PTOL) + assert ae(v.imag, 3.1762093035975914678, tol=PTOL) + v = fp.ei((0.0 + 5.0j)) + assert ae(v, (-0.19002974965664387862 + 3.1207275717395707565j), tol=ATOL) + assert ae(v.real, -0.19002974965664387862, tol=PTOL) + assert ae(v.imag, 3.1207275717395707565, tol=PTOL) + v = fp.ei((0.0 + 20.0j)) + assert ae(v, (0.04441982084535331654 + 3.1190380278383364594j), tol=ATOL) + assert ae(v.real, 0.04441982084535331654, tol=PTOL) + assert ae(v.imag, 3.1190380278383364594, tol=PTOL) + v = fp.ei((0.0 + 30.0j)) + assert ae(v, (-0.033032417282071143779 + 3.1375528668252477302j), tol=ATOL) + assert ae(v.real, -0.033032417282071143779, tol=PTOL) + assert ae(v.imag, 3.1375528668252477302, tol=PTOL) + v = fp.ei((0.0 + 40.0j)) + assert ae(v, (0.019020007896208766962 + 3.157781446149681126j), tol=ATOL) + assert ae(v.real, 0.019020007896208766962, tol=PTOL) + assert ae(v.imag, 3.157781446149681126, tol=PTOL) + v = fp.ei((0.0 + 50.0j)) + assert ae(v, (-0.0056283863241163054402 + 3.122413399280832514j), tol=ATOL) + assert ae(v.real, -0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, 3.122413399280832514, tol=PTOL) + v = fp.ei((0.0 + 80.0j)) + assert ae(v, (-0.012402501155070958192 + 3.1431272137073839346j), tol=ATOL) + assert ae(v.real, -0.012402501155070958192, tol=PTOL) + assert ae(v.imag, 3.1431272137073839346, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 + 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621664969632 + 1.8157749903874220607j), tol=ATOL) + assert ae(v.real, -20.880034621664969632, tol=PTOL) + assert ae(v.imag, 1.8157749903874220607, tol=PTOL) + v = fp.ei((-0.25 + 1.0j)) + assert ae(v, (0.16868306393667788761 + 2.6557914649950505414j), tol=ATOL) + assert ae(v.real, 0.16868306393667788761, tol=PTOL) + assert ae(v.imag, 2.6557914649950505414, tol=PTOL) + v = fp.ei((-1.0 + 4.0j)) + assert ae(v, (-0.03373591813926547318 + 3.2151161058308770603j), tol=ATOL) + assert ae(v.real, -0.03373591813926547318, tol=PTOL) + assert ae(v.imag, 3.2151161058308770603, tol=PTOL) + v = fp.ei((-2.0 + 8.0j)) + assert ae(v, (0.015392833434733785143 + 3.1384179414340326969j), tol=ATOL) + assert ae(v.real, 0.015392833434733785143, tol=PTOL) + assert ae(v.imag, 3.1384179414340326969, tol=PTOL) + v = fp.ei((-5.0 + 20.0j)) + assert ae(v, (0.00024419662286542966525 + 3.1413825703601317109j), tol=ATOL) + assert ae(v.real, 0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, 3.1413825703601317109, tol=PTOL) + v = fp.ei((-20.0 + 80.0j)) + assert ae(v, (-2.3255552781051330088e-11 + 3.1415926535987396304j), tol=ATOL) + assert ae(v.real, -2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, 3.1415926535987396304, tol=PTOL) + v = fp.ei((-30.0 + 120.0j)) + assert ae(v, (2.7068919097124652332e-16 + 3.1415926535897925337j), tol=ATOL) + assert ae(v.real, 2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, 3.1415926535897925337, tol=PTOL) + v = fp.ei((-40.0 + 160.0j)) + assert ae(v, (1.1695597827678024687e-20 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 + 200.0j)) + assert ae(v, (-9.0323746914410162531e-25 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 + 320.0j)) + assert ae(v, (-3.4819106748728063576e-38 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-4.6566128730773925781e-10 + 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034622014215597 + 2.8966139905793444061j), tol=ATOL) + assert ae(v.real, -20.880034622014215597, tol=PTOL) + assert ae(v.imag, 2.8966139905793444061, tol=PTOL) + v = fp.ei((-1.0 + 0.25j)) + assert ae(v, (-0.19731063945004229095 + 3.0542266078154932748j), tol=ATOL) + assert ae(v.real, -0.19731063945004229095, tol=PTOL) + assert ae(v.imag, 3.0542266078154932748, tol=PTOL) + v = fp.ei((-4.0 + 1.0j)) + assert ae(v, (-0.0013106173980145506944 + 3.1381384055698581758j), tol=ATOL) + assert ae(v.real, -0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, 3.1381384055698581758, tol=PTOL) + v = fp.ei((-8.0 + 2.0j)) + assert ae(v, (0.000022278049065270225945 + 3.1415634616493367169j), tol=ATOL) + assert ae(v.real, 0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, 3.1415634616493367169, tol=PTOL) + v = fp.ei((-20.0 + 5.0j)) + assert ae(v, (-4.7711374515765346894e-11 + 3.1415926536726958909j), tol=ATOL) + assert ae(v.real, -4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, 3.1415926536726958909, tol=PTOL) + v = fp.ei((-80.0 + 20.0j)) + assert ae(v, (-3.8353473865788235787e-38 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-120.0 + 30.0j)) + assert ae(v, (-2.3836002337480334716e-55 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-160.0 + 40.0j)) + assert ae(v, (1.6238022898654510661e-72 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-200.0 + 50.0j)) + assert ae(v, (-6.6800061461666228487e-90 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei((-320.0 + 80.0j)) + assert ae(v, (-4.2737871527778786157e-143 + 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, 3.1415926535897932385, tol=PTOL) + v = fp.ei(-1.1641532182693481445e-10) + assert ae(v, -22.296641293693077672, tol=ATOL) + assert type(v) is float + v = fp.ei(-0.25) + assert ae(v, -1.0442826344437381945, tol=ATOL) + assert type(v) is float + v = fp.ei(-1.0) + assert ae(v, -0.21938393439552027368, tol=ATOL) + assert type(v) is float + v = fp.ei(-2.0) + assert ae(v, -0.048900510708061119567, tol=ATOL) + assert type(v) is float + v = fp.ei(-5.0) + assert ae(v, -0.0011482955912753257973, tol=ATOL) + assert type(v) is float + v = fp.ei(-20.0) + assert ae(v, -9.8355252906498816904e-11, tol=ATOL) + assert type(v) is float + v = fp.ei(-30.0) + assert ae(v, -3.0215520106888125448e-15, tol=ATOL) + assert type(v) is float + v = fp.ei(-40.0) + assert ae(v, -1.0367732614516569722e-19, tol=ATOL) + assert type(v) is float + v = fp.ei(-50.0) + assert ae(v, -3.7832640295504590187e-24, tol=ATOL) + assert type(v) is float + v = fp.ei(-80.0) + assert ae(v, -2.2285432586884729112e-37, tol=ATOL) + assert type(v) is float + v = fp.ei((-1.1641532182693481445e-10 + 0.0j)) + assert ae(v, (-22.296641293693077672 + 0.0j), tol=ATOL) + assert ae(v.real, -22.296641293693077672, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-0.25 + 0.0j)) + assert ae(v, (-1.0442826344437381945 + 0.0j), tol=ATOL) + assert ae(v.real, -1.0442826344437381945, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-1.0 + 0.0j)) + assert ae(v, (-0.21938393439552027368 + 0.0j), tol=ATOL) + assert ae(v.real, -0.21938393439552027368, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-2.0 + 0.0j)) + assert ae(v, (-0.048900510708061119567 + 0.0j), tol=ATOL) + assert ae(v.real, -0.048900510708061119567, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-5.0 + 0.0j)) + assert ae(v, (-0.0011482955912753257973 + 0.0j), tol=ATOL) + assert ae(v.real, -0.0011482955912753257973, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-20.0 + 0.0j)) + assert ae(v, (-9.8355252906498816904e-11 + 0.0j), tol=ATOL) + assert ae(v.real, -9.8355252906498816904e-11, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-30.0 + 0.0j)) + assert ae(v, (-3.0215520106888125448e-15 + 0.0j), tol=ATOL) + assert ae(v.real, -3.0215520106888125448e-15, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-40.0 + 0.0j)) + assert ae(v, (-1.0367732614516569722e-19 + 0.0j), tol=ATOL) + assert ae(v.real, -1.0367732614516569722e-19, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-50.0 + 0.0j)) + assert ae(v, (-3.7832640295504590187e-24 + 0.0j), tol=ATOL) + assert ae(v.real, -3.7832640295504590187e-24, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-80.0 + 0.0j)) + assert ae(v, (-2.2285432586884729112e-37 + 0.0j), tol=ATOL) + assert ae(v.real, -2.2285432586884729112e-37, tol=PTOL) + assert v.imag == 0 + v = fp.ei((-4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034622014215597 - 2.8966139905793444061j), tol=ATOL) + assert ae(v.real, -20.880034622014215597, tol=PTOL) + assert ae(v.imag, -2.8966139905793444061, tol=PTOL) + v = fp.ei((-1.0 - 0.25j)) + assert ae(v, (-0.19731063945004229095 - 3.0542266078154932748j), tol=ATOL) + assert ae(v.real, -0.19731063945004229095, tol=PTOL) + assert ae(v.imag, -3.0542266078154932748, tol=PTOL) + v = fp.ei((-4.0 - 1.0j)) + assert ae(v, (-0.0013106173980145506944 - 3.1381384055698581758j), tol=ATOL) + assert ae(v.real, -0.0013106173980145506944, tol=PTOL) + assert ae(v.imag, -3.1381384055698581758, tol=PTOL) + v = fp.ei((-8.0 - 2.0j)) + assert ae(v, (0.000022278049065270225945 - 3.1415634616493367169j), tol=ATOL) + assert ae(v.real, 0.000022278049065270225945, tol=PTOL) + assert ae(v.imag, -3.1415634616493367169, tol=PTOL) + v = fp.ei((-20.0 - 5.0j)) + assert ae(v, (-4.7711374515765346894e-11 - 3.1415926536726958909j), tol=ATOL) + assert ae(v.real, -4.7711374515765346894e-11, tol=PTOL) + assert ae(v.imag, -3.1415926536726958909, tol=PTOL) + v = fp.ei((-80.0 - 20.0j)) + assert ae(v, (-3.8353473865788235787e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.8353473865788235787e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-120.0 - 30.0j)) + assert ae(v, (-2.3836002337480334716e-55 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3836002337480334716e-55, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-160.0 - 40.0j)) + assert ae(v, (1.6238022898654510661e-72 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.6238022898654510661e-72, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-200.0 - 50.0j)) + assert ae(v, (-6.6800061461666228487e-90 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -6.6800061461666228487e-90, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-320.0 - 80.0j)) + assert ae(v, (-4.2737871527778786157e-143 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -4.2737871527778786157e-143, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703413105017 - 2.3561944903087602507j), tol=ATOL) + assert ae(v.real, -21.950067703413105017, tol=PTOL) + assert ae(v.imag, -2.3561944903087602507, tol=PTOL) + v = fp.ei((-0.25 - 0.25j)) + assert ae(v, (-0.71092525792923287894 - 2.5766745291767512913j), tol=ATOL) + assert ae(v.real, -0.71092525792923287894, tol=PTOL) + assert ae(v.imag, -2.5766745291767512913, tol=PTOL) + v = fp.ei((-1.0 - 1.0j)) + assert ae(v, (-0.00028162445198141832551 - 2.9622681185504342983j), tol=ATOL) + assert ae(v.real, -0.00028162445198141832551, tol=PTOL) + assert ae(v.imag, -2.9622681185504342983, tol=PTOL) + v = fp.ei((-2.0 - 2.0j)) + assert ae(v, (0.033767089606562004246 - 3.1229932394200426965j), tol=ATOL) + assert ae(v.real, 0.033767089606562004246, tol=PTOL) + assert ae(v.imag, -3.1229932394200426965, tol=PTOL) + v = fp.ei((-5.0 - 5.0j)) + assert ae(v, (-0.0007266506660356393891 - 3.1420636813914284609j), tol=ATOL) + assert ae(v.real, -0.0007266506660356393891, tol=PTOL) + assert ae(v.imag, -3.1420636813914284609, tol=PTOL) + v = fp.ei((-20.0 - 20.0j)) + assert ae(v, (2.3824537449367396579e-11 - 3.1415926535228233653j), tol=ATOL) + assert ae(v.real, 2.3824537449367396579e-11, tol=PTOL) + assert ae(v.imag, -3.1415926535228233653, tol=PTOL) + v = fp.ei((-30.0 - 30.0j)) + assert ae(v, (-1.7316045841744061617e-15 - 3.141592653589794545j), tol=ATOL) + assert ae(v.real, -1.7316045841744061617e-15, tol=PTOL) + assert ae(v.imag, -3.141592653589794545, tol=PTOL) + v = fp.ei((-40.0 - 40.0j)) + assert ae(v, (7.4001043002899232182e-20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 7.4001043002899232182e-20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 - 50.0j)) + assert ae(v, (-2.3566128324644641219e-24 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -2.3566128324644641219e-24, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 - 80.0j)) + assert ae(v, (-9.8279750572186526673e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.8279750572186526673e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621664969632 - 1.8157749903874220607j), tol=ATOL) + assert ae(v.real, -20.880034621664969632, tol=PTOL) + assert ae(v.imag, -1.8157749903874220607, tol=PTOL) + v = fp.ei((-0.25 - 1.0j)) + assert ae(v, (0.16868306393667788761 - 2.6557914649950505414j), tol=ATOL) + assert ae(v.real, 0.16868306393667788761, tol=PTOL) + assert ae(v.imag, -2.6557914649950505414, tol=PTOL) + v = fp.ei((-1.0 - 4.0j)) + assert ae(v, (-0.03373591813926547318 - 3.2151161058308770603j), tol=ATOL) + assert ae(v.real, -0.03373591813926547318, tol=PTOL) + assert ae(v.imag, -3.2151161058308770603, tol=PTOL) + v = fp.ei((-2.0 - 8.0j)) + assert ae(v, (0.015392833434733785143 - 3.1384179414340326969j), tol=ATOL) + assert ae(v.real, 0.015392833434733785143, tol=PTOL) + assert ae(v.imag, -3.1384179414340326969, tol=PTOL) + v = fp.ei((-5.0 - 20.0j)) + assert ae(v, (0.00024419662286542966525 - 3.1413825703601317109j), tol=ATOL) + assert ae(v.real, 0.00024419662286542966525, tol=PTOL) + assert ae(v.imag, -3.1413825703601317109, tol=PTOL) + v = fp.ei((-20.0 - 80.0j)) + assert ae(v, (-2.3255552781051330088e-11 - 3.1415926535987396304j), tol=ATOL) + assert ae(v.real, -2.3255552781051330088e-11, tol=PTOL) + assert ae(v.imag, -3.1415926535987396304, tol=PTOL) + v = fp.ei((-30.0 - 120.0j)) + assert ae(v, (2.7068919097124652332e-16 - 3.1415926535897925337j), tol=ATOL) + assert ae(v.real, 2.7068919097124652332e-16, tol=PTOL) + assert ae(v.imag, -3.1415926535897925337, tol=PTOL) + v = fp.ei((-40.0 - 160.0j)) + assert ae(v, (1.1695597827678024687e-20 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, 1.1695597827678024687e-20, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-50.0 - 200.0j)) + assert ae(v, (-9.0323746914410162531e-25 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -9.0323746914410162531e-25, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((-80.0 - 320.0j)) + assert ae(v, (-3.4819106748728063576e-38 - 3.1415926535897932385j), tol=ATOL) + assert ae(v.real, -3.4819106748728063576e-38, tol=PTOL) + assert ae(v.imag, -3.1415926535897932385, tol=PTOL) + v = fp.ei((0.0 - 1.1641532182693481445e-10j)) + assert ae(v, (-22.29664129357666235 - 1.5707963269113119411j), tol=ATOL) + assert ae(v.real, -22.29664129357666235, tol=PTOL) + assert ae(v.imag, -1.5707963269113119411, tol=PTOL) + v = fp.ei((0.0 - 0.25j)) + assert ae(v, (-0.82466306258094565309 - 1.8199298971146537833j), tol=ATOL) + assert ae(v.real, -0.82466306258094565309, tol=PTOL) + assert ae(v.imag, -1.8199298971146537833, tol=PTOL) + v = fp.ei((0.0 - 1.0j)) + assert ae(v, (0.33740392290096813466 - 2.5168793971620796342j), tol=ATOL) + assert ae(v.real, 0.33740392290096813466, tol=PTOL) + assert ae(v.imag, -2.5168793971620796342, tol=PTOL) + v = fp.ei((0.0 - 2.0j)) + assert ae(v, (0.4229808287748649957 - 3.1762093035975914678j), tol=ATOL) + assert ae(v.real, 0.4229808287748649957, tol=PTOL) + assert ae(v.imag, -3.1762093035975914678, tol=PTOL) + v = fp.ei((0.0 - 5.0j)) + assert ae(v, (-0.19002974965664387862 - 3.1207275717395707565j), tol=ATOL) + assert ae(v.real, -0.19002974965664387862, tol=PTOL) + assert ae(v.imag, -3.1207275717395707565, tol=PTOL) + v = fp.ei((0.0 - 20.0j)) + assert ae(v, (0.04441982084535331654 - 3.1190380278383364594j), tol=ATOL) + assert ae(v.real, 0.04441982084535331654, tol=PTOL) + assert ae(v.imag, -3.1190380278383364594, tol=PTOL) + v = fp.ei((0.0 - 30.0j)) + assert ae(v, (-0.033032417282071143779 - 3.1375528668252477302j), tol=ATOL) + assert ae(v.real, -0.033032417282071143779, tol=PTOL) + assert ae(v.imag, -3.1375528668252477302, tol=PTOL) + v = fp.ei((0.0 - 40.0j)) + assert ae(v, (0.019020007896208766962 - 3.157781446149681126j), tol=ATOL) + assert ae(v.real, 0.019020007896208766962, tol=PTOL) + assert ae(v.imag, -3.157781446149681126, tol=PTOL) + v = fp.ei((0.0 - 50.0j)) + assert ae(v, (-0.0056283863241163054402 - 3.122413399280832514j), tol=ATOL) + assert ae(v.real, -0.0056283863241163054402, tol=PTOL) + assert ae(v.imag, -3.122413399280832514, tol=PTOL) + v = fp.ei((0.0 - 80.0j)) + assert ae(v, (-0.012402501155070958192 - 3.1431272137073839346j), tol=ATOL) + assert ae(v.real, -0.012402501155070958192, tol=PTOL) + assert ae(v.imag, -3.1431272137073839346, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 - 4.6566128730773925781e-10j)) + assert ae(v, (-20.880034621432138988 - 1.3258176641336937524j), tol=ATOL) + assert ae(v.real, -20.880034621432138988, tol=PTOL) + assert ae(v.imag, -1.3258176641336937524, tol=PTOL) + v = fp.ei((0.25 - 1.0j)) + assert ae(v, (0.59066621214766308594 - 2.3968481059377428687j), tol=ATOL) + assert ae(v.real, 0.59066621214766308594, tol=PTOL) + assert ae(v.imag, -2.3968481059377428687, tol=PTOL) + v = fp.ei((1.0 - 4.0j)) + assert ae(v, (-0.49739047283060471093 - 3.5570287076301818702j), tol=ATOL) + assert ae(v.real, -0.49739047283060471093, tol=PTOL) + assert ae(v.imag, -3.5570287076301818702, tol=PTOL) + v = fp.ei((2.0 - 8.0j)) + assert ae(v, (0.8705211147733730969 - 3.3825859385758486351j), tol=ATOL) + assert ae(v.real, 0.8705211147733730969, tol=PTOL) + assert ae(v.imag, -3.3825859385758486351, tol=PTOL) + v = fp.ei((5.0 - 20.0j)) + assert ae(v, (7.0789514293925893007 - 1.5313749363937141849j), tol=ATOL) + assert ae(v.real, 7.0789514293925893007, tol=PTOL) + assert ae(v.imag, -1.5313749363937141849, tol=PTOL) + v = fp.ei((20.0 - 80.0j)) + assert ae(v, (-5855431.4907298084434 + 720917.79156143806727j), tol=ATOL) + assert ae(v.real, -5855431.4907298084434, tol=PTOL) + assert ae(v.imag, 720917.79156143806727, tol=PTOL) + v = fp.ei((30.0 - 120.0j)) + assert ae(v, (65402491644.703470747 + 56697658396.51586764j), tol=ATOL) + assert ae(v.real, 65402491644.703470747, tol=PTOL) + assert ae(v.imag, 56697658396.51586764, tol=PTOL) + v = fp.ei((40.0 - 160.0j)) + assert ae(v, (-25504929379604.776769 - 1429035198630576.3879j), tol=ATOL) + assert ae(v.real, -25504929379604.776769, tol=PTOL) + assert ae(v.imag, -1429035198630576.3879, tol=PTOL) + v = fp.ei((50.0 - 200.0j)) + assert ae(v, (-18437746526988116954.0 + 17146362239046152342.0j), tol=ATOL) + assert ae(v.real, -18437746526988116954.0, tol=PTOL) + assert ae(v.imag, 17146362239046152342.0, tol=PTOL) + v = fp.ei((80.0 - 320.0j)) + assert ae(v, (-3.3464697299634526706e+31 + 1.6473152633843023919e+32j), tol=ATOL) + assert ae(v.real, -3.3464697299634526706e+31, tol=PTOL) + assert ae(v.imag, 1.6473152633843023919e+32, tol=PTOL) + v = fp.ei((1.1641532182693481445e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-21.950067703180274374 - 0.78539816351386363145j), tol=ATOL) + assert ae(v.real, -21.950067703180274374, tol=PTOL) + assert ae(v.imag, -0.78539816351386363145, tol=PTOL) + v = fp.ei((0.25 - 0.25j)) + assert ae(v, (-0.21441047326710323254 - 1.0683772981589995996j), tol=ATOL) + assert ae(v.real, -0.21441047326710323254, tol=PTOL) + assert ae(v.imag, -1.0683772981589995996, tol=PTOL) + v = fp.ei((1.0 - 1.0j)) + assert ae(v, (1.7646259855638540684 - 2.3877698515105224193j), tol=ATOL) + assert ae(v.real, 1.7646259855638540684, tol=PTOL) + assert ae(v.imag, -2.3877698515105224193, tol=PTOL) + v = fp.ei((2.0 - 2.0j)) + assert ae(v, (1.8920781621855474089 - 5.3169624378326579621j), tol=ATOL) + assert ae(v.real, 1.8920781621855474089, tol=PTOL) + assert ae(v.imag, -5.3169624378326579621, tol=PTOL) + v = fp.ei((5.0 - 5.0j)) + assert ae(v, (-13.470936071475245856 + 15.322492395731230968j), tol=ATOL) + assert ae(v.real, -13.470936071475245856, tol=PTOL) + assert ae(v.imag, 15.322492395731230968, tol=PTOL) + v = fp.ei((20.0 - 20.0j)) + assert ae(v, (16589317.398788971896 - 5831705.4712368307104j), tol=ATOL) + assert ae(v.real, 16589317.398788971896, tol=PTOL) + assert ae(v.imag, -5831705.4712368307104, tol=PTOL) + v = fp.ei((30.0 - 30.0j)) + assert ae(v, (-154596484273.69322527 + 204179357834.2723043j), tol=ATOL) + assert ae(v.real, -154596484273.69322527, tol=PTOL) + assert ae(v.imag, 204179357834.2723043, tol=PTOL) + v = fp.ei((40.0 - 40.0j)) + assert ae(v, (287512180321448.45408 - 4203502407932318.1156j), tol=ATOL) + assert ae(v.real, 287512180321448.45408, tol=PTOL) + assert ae(v.imag, -4203502407932318.1156, tol=PTOL) + v = fp.ei((50.0 - 50.0j)) + assert ae(v, (36128528616649268826.0 + 64648801861338741960.0j), tol=ATOL) + assert ae(v.real, 36128528616649268826.0, tol=PTOL) + assert ae(v.imag, 64648801861338741960.0, tol=PTOL) + v = fp.ei((80.0 - 80.0j)) + assert ae(v, (-3.8674816337930010217e+32 + 3.0540709639658071041e+32j), tol=ATOL) + assert ae(v.real, -3.8674816337930010217e+32, tol=PTOL) + assert ae(v.imag, 3.0540709639658071041e+32, tol=PTOL) + v = fp.ei((4.6566128730773925781e-10 - 1.1641532182693481445e-10j)) + assert ae(v, (-20.880034621082893023 - 0.24497866324327947603j), tol=ATOL) + assert ae(v.real, -20.880034621082893023, tol=PTOL) + assert ae(v.imag, -0.24497866324327947603, tol=PTOL) + v = fp.ei((1.0 - 0.25j)) + assert ae(v, (1.8942716983721074932 - 0.67268237088273915854j), tol=ATOL) + assert ae(v.real, 1.8942716983721074932, tol=PTOL) + assert ae(v.imag, -0.67268237088273915854, tol=PTOL) + v = fp.ei((4.0 - 1.0j)) + assert ae(v, (14.806699492675420438 - 12.280015176673582616j), tol=ATOL) + assert ae(v.real, 14.806699492675420438, tol=PTOL) + assert ae(v.imag, -12.280015176673582616, tol=PTOL) + v = fp.ei((8.0 - 2.0j)) + assert ae(v, (-54.633252667426386294 - 416.34477429173650012j), tol=ATOL) + assert ae(v.real, -54.633252667426386294, tol=PTOL) + assert ae(v.imag, -416.34477429173650012, tol=PTOL) + v = fp.ei((20.0 - 5.0j)) + assert ae(v, (711836.97165402624643 + 24745247.798103247366j), tol=ATOL) + assert ae(v.real, 711836.97165402624643, tol=PTOL) + assert ae(v.imag, 24745247.798103247366, tol=PTOL) + v = fp.ei((80.0 - 20.0j)) + assert ae(v, (4.2139911108612653091e+32 - 5.3367124741918251637e+32j), tol=ATOL) + assert ae(v.real, 4.2139911108612653091e+32, tol=PTOL) + assert ae(v.imag, -5.3367124741918251637e+32, tol=PTOL) + v = fp.ei((120.0 - 30.0j)) + assert ae(v, (-9.7760616203707508892e+48 + 1.058257682317195792e+50j), tol=ATOL) + assert ae(v.real, -9.7760616203707508892e+48, tol=PTOL) + assert ae(v.imag, 1.058257682317195792e+50, tol=PTOL) + v = fp.ei((160.0 - 40.0j)) + assert ae(v, (-8.7065541466623638861e+66 - 1.6577106725141739889e+67j), tol=ATOL) + assert ae(v.real, -8.7065541466623638861e+66, tol=PTOL) + assert ae(v.imag, -1.6577106725141739889e+67, tol=PTOL) + v = fp.ei((200.0 - 50.0j)) + assert ae(v, (3.070744996327018106e+84 + 1.7243244846769415903e+84j), tol=ATOL) + assert ae(v.real, 3.070744996327018106e+84, tol=PTOL) + assert ae(v.imag, 1.7243244846769415903e+84, tol=PTOL) + v = fp.ei((320.0 - 80.0j)) + assert ae(v, (-9.9960598637998647276e+135 + 2.6855081527595608863e+136j), tol=ATOL) + assert ae(v.real, -9.9960598637998647276e+135, tol=PTOL) + assert ae(v.imag, 2.6855081527595608863e+136, tol=PTOL) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..3bfe852f008173eb636c147abc83d71dbdd4d23a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions.py @@ -0,0 +1,920 @@ +from mpmath.libmp import * +from mpmath import * +import random +import time +import math +import cmath + +def mpc_ae(a, b, eps=eps): + res = True + res = res and a.real.ae(b.real, eps) + res = res and a.imag.ae(b.imag, eps) + return res + +#---------------------------------------------------------------------------- +# Constants and functions +# + +tpi = "3.1415926535897932384626433832795028841971693993751058209749445923078\ +1640628620899862803482534211706798" +te = "2.71828182845904523536028747135266249775724709369995957496696762772407\ +663035354759457138217852516642743" +tdegree = "0.017453292519943295769236907684886127134428718885417254560971914\ +4017100911460344944368224156963450948221" +teuler = "0.5772156649015328606065120900824024310421593359399235988057672348\ +84867726777664670936947063291746749516" +tln2 = "0.693147180559945309417232121458176568075500134360255254120680009493\ +393621969694715605863326996418687542" +tln10 = "2.30258509299404568401799145468436420760110148862877297603332790096\ +757260967735248023599720508959829834" +tcatalan = "0.91596559417721901505460351493238411077414937428167213426649811\ +9621763019776254769479356512926115106249" +tkhinchin = "2.6854520010653064453097148354817956938203822939944629530511523\ +4555721885953715200280114117493184769800" +tglaisher = "1.2824271291006226368753425688697917277676889273250011920637400\ +2174040630885882646112973649195820237439420646" +tapery = "1.2020569031595942853997381615114499907649862923404988817922715553\ +4183820578631309018645587360933525815" +tphi = "1.618033988749894848204586834365638117720309179805762862135448622705\ +26046281890244970720720418939113748475" +tmertens = "0.26149721284764278375542683860869585905156664826119920619206421\ +3924924510897368209714142631434246651052" +ttwinprime = "0.660161815846869573927812110014555778432623360284733413319448\ +423335405642304495277143760031413839867912" + +def test_constants(): + for prec in [3, 7, 10, 15, 20, 37, 80, 100, 29]: + mp.dps = prec + assert pi == mpf(tpi) + assert e == mpf(te) + assert degree == mpf(tdegree) + assert euler == mpf(teuler) + assert ln2 == mpf(tln2) + assert ln10 == mpf(tln10) + assert catalan == mpf(tcatalan) + assert khinchin == mpf(tkhinchin) + assert glaisher == mpf(tglaisher) + assert phi == mpf(tphi) + if prec < 50: + assert mertens == mpf(tmertens) + assert twinprime == mpf(ttwinprime) + mp.dps = 15 + assert pi >= -1 + assert pi > 2 + assert pi > 3 + assert pi < 4 + +def test_exact_sqrts(): + for i in range(20000): + assert sqrt(mpf(i*i)) == i + random.seed(1) + for prec in [100, 300, 1000, 10000]: + mp.dps = prec + for i in range(20): + A = random.randint(10**(prec//2-2), 10**(prec//2-1)) + assert sqrt(mpf(A*A)) == A + mp.dps = 15 + for i in range(100): + for a in [1, 8, 25, 112307]: + assert sqrt(mpf((a*a, 2*i))) == mpf((a, i)) + assert sqrt(mpf((a*a, -2*i))) == mpf((a, -i)) + +def test_sqrt_rounding(): + for i in [2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]: + i = from_int(i) + for dps in [7, 15, 83, 106, 2000]: + mp.dps = dps + a = mpf_pow_int(mpf_sqrt(i, mp.prec, round_down), 2, mp.prec, round_down) + b = mpf_pow_int(mpf_sqrt(i, mp.prec, round_up), 2, mp.prec, round_up) + assert mpf_lt(a, i) + assert mpf_gt(b, i) + random.seed(1234) + prec = 100 + for rnd in [round_down, round_nearest, round_ceiling]: + for i in range(100): + a = mpf_rand(prec) + b = mpf_mul(a, a) + assert mpf_sqrt(b, prec, rnd) == a + # Test some extreme cases + mp.dps = 100 + a = mpf(9) + 1e-90 + b = mpf(9) - 1e-90 + mp.dps = 15 + assert sqrt(a, rounding='d') == 3 + assert sqrt(a, rounding='n') == 3 + assert sqrt(a, rounding='u') > 3 + assert sqrt(b, rounding='d') < 3 + assert sqrt(b, rounding='n') == 3 + assert sqrt(b, rounding='u') == 3 + # A worst case, from the MPFR test suite + assert sqrt(mpf('7.0503726185518891')) == mpf('2.655253776675949') + +def test_float_sqrt(): + mp.dps = 15 + # These should round identically + for x in [0, 1e-7, 0.1, 0.5, 1, 2, 3, 4, 5, 0.333, 76.19]: + assert sqrt(mpf(x)) == float(x)**0.5 + assert sqrt(-1) == 1j + assert sqrt(-2).ae(cmath.sqrt(-2)) + assert sqrt(-3).ae(cmath.sqrt(-3)) + assert sqrt(-100).ae(cmath.sqrt(-100)) + assert sqrt(1j).ae(cmath.sqrt(1j)) + assert sqrt(-1j).ae(cmath.sqrt(-1j)) + assert sqrt(math.pi + math.e*1j).ae(cmath.sqrt(math.pi + math.e*1j)) + assert sqrt(math.pi - math.e*1j).ae(cmath.sqrt(math.pi - math.e*1j)) + +def test_hypot(): + assert hypot(0, 0) == 0 + assert hypot(0, 0.33) == mpf(0.33) + assert hypot(0.33, 0) == mpf(0.33) + assert hypot(-0.33, 0) == mpf(0.33) + assert hypot(3, 4) == mpf(5) + +def test_exact_cbrt(): + for i in range(0, 20000, 200): + assert cbrt(mpf(i*i*i)) == i + random.seed(1) + for prec in [100, 300, 1000, 10000]: + mp.dps = prec + A = random.randint(10**(prec//2-2), 10**(prec//2-1)) + assert cbrt(mpf(A*A*A)) == A + mp.dps = 15 + +def test_exp(): + assert exp(0) == 1 + assert exp(10000).ae(mpf('8.8068182256629215873e4342')) + assert exp(-10000).ae(mpf('1.1354838653147360985e-4343')) + a = exp(mpf((1, 8198646019315405, -53, 53))) + assert(a.bc == bitcount(a.man)) + mp.prec = 67 + a = exp(mpf((1, 1781864658064754565, -60, 61))) + assert(a.bc == bitcount(a.man)) + mp.prec = 53 + assert exp(ln2 * 10).ae(1024) + assert exp(2+2j).ae(cmath.exp(2+2j)) + +def test_issue_73(): + mp.dps = 512 + a = exp(-1) + b = exp(1) + mp.dps = 15 + assert (+a).ae(0.36787944117144233) + assert (+b).ae(2.7182818284590451) + +def test_log(): + mp.dps = 15 + assert log(1) == 0 + for x in [0.5, 1.5, 2.0, 3.0, 100, 10**50, 1e-50]: + assert log(x).ae(math.log(x)) + assert log(x, x) == 1 + assert log(1024, 2) == 10 + assert log(10**1234, 10) == 1234 + assert log(2+2j).ae(cmath.log(2+2j)) + # Accuracy near 1 + assert (log(0.6+0.8j).real*10**17).ae(2.2204460492503131) + assert (log(0.6-0.8j).real*10**17).ae(2.2204460492503131) + assert (log(0.8-0.6j).real*10**17).ae(2.2204460492503131) + assert (log(1+1e-8j).real*10**16).ae(0.5) + assert (log(1-1e-8j).real*10**16).ae(0.5) + assert (log(-1+1e-8j).real*10**16).ae(0.5) + assert (log(-1-1e-8j).real*10**16).ae(0.5) + assert (log(1j+1e-8).real*10**16).ae(0.5) + assert (log(1j-1e-8).real*10**16).ae(0.5) + assert (log(-1j+1e-8).real*10**16).ae(0.5) + assert (log(-1j-1e-8).real*10**16).ae(0.5) + assert (log(1+1e-40j).real*10**80).ae(0.5) + assert (log(1j+1e-40).real*10**80).ae(0.5) + # Huge + assert log(ldexp(1.234,10**20)).ae(log(2)*1e20) + assert log(ldexp(1.234,10**200)).ae(log(2)*1e200) + # Some special values + assert log(mpc(0,0)) == mpc(-inf,0) + assert isnan(log(mpc(nan,0)).real) + assert isnan(log(mpc(nan,0)).imag) + assert isnan(log(mpc(0,nan)).real) + assert isnan(log(mpc(0,nan)).imag) + assert isnan(log(mpc(nan,1)).real) + assert isnan(log(mpc(nan,1)).imag) + assert isnan(log(mpc(1,nan)).real) + assert isnan(log(mpc(1,nan)).imag) + +def test_trig_hyperb_basic(): + for x in (list(range(100)) + list(range(-100,0))): + t = x / 4.1 + assert cos(mpf(t)).ae(math.cos(t)) + assert sin(mpf(t)).ae(math.sin(t)) + assert tan(mpf(t)).ae(math.tan(t)) + assert cosh(mpf(t)).ae(math.cosh(t)) + assert sinh(mpf(t)).ae(math.sinh(t)) + assert tanh(mpf(t)).ae(math.tanh(t)) + assert sin(1+1j).ae(cmath.sin(1+1j)) + assert sin(-4-3.6j).ae(cmath.sin(-4-3.6j)) + assert cos(1+1j).ae(cmath.cos(1+1j)) + assert cos(-4-3.6j).ae(cmath.cos(-4-3.6j)) + +def test_degrees(): + assert cos(0*degree) == 1 + assert cos(90*degree).ae(0) + assert cos(180*degree).ae(-1) + assert cos(270*degree).ae(0) + assert cos(360*degree).ae(1) + assert sin(0*degree) == 0 + assert sin(90*degree).ae(1) + assert sin(180*degree).ae(0) + assert sin(270*degree).ae(-1) + assert sin(360*degree).ae(0) + +def random_complexes(N): + random.seed(1) + a = [] + for i in range(N): + x1 = random.uniform(-10, 10) + y1 = random.uniform(-10, 10) + x2 = random.uniform(-10, 10) + y2 = random.uniform(-10, 10) + z1 = complex(x1, y1) + z2 = complex(x2, y2) + a.append((z1, z2)) + return a + +def test_complex_powers(): + for dps in [15, 30, 100]: + # Check accuracy for complex square root + mp.dps = dps + a = mpc(1j)**0.5 + assert a.real == a.imag == mpf(2)**0.5 / 2 + mp.dps = 15 + random.seed(1) + for (z1, z2) in random_complexes(100): + assert (mpc(z1)**mpc(z2)).ae(z1**z2, 1e-12) + assert (e**(-pi*1j)).ae(-1) + mp.dps = 50 + assert (e**(-pi*1j)).ae(-1) + mp.dps = 15 + +def test_complex_sqrt_accuracy(): + def test_mpc_sqrt(lst): + for a, b in lst: + z = mpc(a + j*b) + assert mpc_ae(sqrt(z*z), z) + z = mpc(-a + j*b) + assert mpc_ae(sqrt(z*z), -z) + z = mpc(a - j*b) + assert mpc_ae(sqrt(z*z), z) + z = mpc(-a - j*b) + assert mpc_ae(sqrt(z*z), -z) + random.seed(2) + N = 10 + mp.dps = 30 + dps = mp.dps + test_mpc_sqrt([(random.uniform(0, 10),random.uniform(0, 10)) for i in range(N)]) + test_mpc_sqrt([(i + 0.1, (i + 0.2)*10**i) for i in range(N)]) + mp.dps = 15 + +def test_atan(): + mp.dps = 15 + assert atan(-2.3).ae(math.atan(-2.3)) + assert atan(1e-50) == 1e-50 + assert atan(1e50).ae(pi/2) + assert atan(-1e-50) == -1e-50 + assert atan(-1e50).ae(-pi/2) + assert atan(10**1000).ae(pi/2) + for dps in [25, 70, 100, 300, 1000]: + mp.dps = dps + assert (4*atan(1)).ae(pi) + mp.dps = 15 + pi2 = pi/2 + assert atan(mpc(inf,-1)).ae(pi2) + assert atan(mpc(inf,0)).ae(pi2) + assert atan(mpc(inf,1)).ae(pi2) + assert atan(mpc(1,inf)).ae(pi2) + assert atan(mpc(0,inf)).ae(pi2) + assert atan(mpc(-1,inf)).ae(-pi2) + assert atan(mpc(-inf,1)).ae(-pi2) + assert atan(mpc(-inf,0)).ae(-pi2) + assert atan(mpc(-inf,-1)).ae(-pi2) + assert atan(mpc(-1,-inf)).ae(-pi2) + assert atan(mpc(0,-inf)).ae(-pi2) + assert atan(mpc(1,-inf)).ae(pi2) + +def test_atan2(): + mp.dps = 15 + assert atan2(1,1).ae(pi/4) + assert atan2(1,-1).ae(3*pi/4) + assert atan2(-1,-1).ae(-3*pi/4) + assert atan2(-1,1).ae(-pi/4) + assert atan2(-1,0).ae(-pi/2) + assert atan2(1,0).ae(pi/2) + assert atan2(0,0) == 0 + assert atan2(inf,0).ae(pi/2) + assert atan2(-inf,0).ae(-pi/2) + assert isnan(atan2(inf,inf)) + assert isnan(atan2(-inf,inf)) + assert isnan(atan2(inf,-inf)) + assert isnan(atan2(3,nan)) + assert isnan(atan2(nan,3)) + assert isnan(atan2(0,nan)) + assert isnan(atan2(nan,0)) + assert atan2(0,inf) == 0 + assert atan2(0,-inf).ae(pi) + assert atan2(10,inf) == 0 + assert atan2(-10,inf) == 0 + assert atan2(-10,-inf).ae(-pi) + assert atan2(10,-inf).ae(pi) + assert atan2(inf,10).ae(pi/2) + assert atan2(inf,-10).ae(pi/2) + assert atan2(-inf,10).ae(-pi/2) + assert atan2(-inf,-10).ae(-pi/2) + +def test_areal_inverses(): + assert asin(mpf(0)) == 0 + assert asinh(mpf(0)) == 0 + assert acosh(mpf(1)) == 0 + assert isinstance(asin(mpf(0.5)), mpf) + assert isinstance(asin(mpf(2.0)), mpc) + assert isinstance(acos(mpf(0.5)), mpf) + assert isinstance(acos(mpf(2.0)), mpc) + assert isinstance(atanh(mpf(0.1)), mpf) + assert isinstance(atanh(mpf(1.1)), mpc) + + random.seed(1) + for i in range(50): + x = random.uniform(0, 1) + assert asin(mpf(x)).ae(math.asin(x)) + assert acos(mpf(x)).ae(math.acos(x)) + + x = random.uniform(-10, 10) + assert asinh(mpf(x)).ae(cmath.asinh(x).real) + assert isinstance(asinh(mpf(x)), mpf) + x = random.uniform(1, 10) + assert acosh(mpf(x)).ae(cmath.acosh(x).real) + assert isinstance(acosh(mpf(x)), mpf) + x = random.uniform(-10, 0.999) + assert isinstance(acosh(mpf(x)), mpc) + + x = random.uniform(-1, 1) + assert atanh(mpf(x)).ae(cmath.atanh(x).real) + assert isinstance(atanh(mpf(x)), mpf) + + dps = mp.dps + mp.dps = 300 + assert isinstance(asin(0.5), mpf) + mp.dps = 1000 + assert asin(1).ae(pi/2) + assert asin(-1).ae(-pi/2) + mp.dps = dps + +def test_invhyperb_inaccuracy(): + mp.dps = 15 + assert (asinh(1e-5)*10**5).ae(0.99999999998333333) + assert (asinh(1e-10)*10**10).ae(1) + assert (asinh(1e-50)*10**50).ae(1) + assert (asinh(-1e-5)*10**5).ae(-0.99999999998333333) + assert (asinh(-1e-10)*10**10).ae(-1) + assert (asinh(-1e-50)*10**50).ae(-1) + assert asinh(10**20).ae(46.744849040440862) + assert asinh(-10**20).ae(-46.744849040440862) + assert (tanh(1e-10)*10**10).ae(1) + assert (tanh(-1e-10)*10**10).ae(-1) + assert (atanh(1e-10)*10**10).ae(1) + assert (atanh(-1e-10)*10**10).ae(-1) + +def test_complex_functions(): + for x in (list(range(10)) + list(range(-10,0))): + for y in (list(range(10)) + list(range(-10,0))): + z = complex(x, y)/4.3 + 0.01j + assert exp(mpc(z)).ae(cmath.exp(z)) + assert log(mpc(z)).ae(cmath.log(z)) + assert cos(mpc(z)).ae(cmath.cos(z)) + assert sin(mpc(z)).ae(cmath.sin(z)) + assert tan(mpc(z)).ae(cmath.tan(z)) + assert sinh(mpc(z)).ae(cmath.sinh(z)) + assert cosh(mpc(z)).ae(cmath.cosh(z)) + assert tanh(mpc(z)).ae(cmath.tanh(z)) + +def test_complex_inverse_functions(): + mp.dps = 15 + iv.dps = 15 + for (z1, z2) in random_complexes(30): + # apparently cmath uses a different branch, so we + # can't use it for comparison + assert sinh(asinh(z1)).ae(z1) + # + assert acosh(z1).ae(cmath.acosh(z1)) + assert atanh(z1).ae(cmath.atanh(z1)) + assert atan(z1).ae(cmath.atan(z1)) + # the reason we set a big eps here is that the cmath + # functions are inaccurate + assert asin(z1).ae(cmath.asin(z1), rel_eps=1e-12) + assert acos(z1).ae(cmath.acos(z1), rel_eps=1e-12) + one = mpf(1) + for i in range(-9, 10, 3): + for k in range(-9, 10, 3): + a = 0.9*j*10**k + 0.8*one*10**i + b = cos(acos(a)) + assert b.ae(a) + b = sin(asin(a)) + assert b.ae(a) + one = mpf(1) + err = 2*10**-15 + for i in range(-9, 9, 3): + for k in range(-9, 9, 3): + a = -0.9*10**k + j*0.8*one*10**i + b = cosh(acosh(a)) + assert b.ae(a, err) + b = sinh(asinh(a)) + assert b.ae(a, err) + +def test_reciprocal_functions(): + assert sec(3).ae(-1.01010866590799375) + assert csc(3).ae(7.08616739573718592) + assert cot(3).ae(-7.01525255143453347) + assert sech(3).ae(0.0993279274194332078) + assert csch(3).ae(0.0998215696688227329) + assert coth(3).ae(1.00496982331368917) + assert asec(3).ae(1.23095941734077468) + assert acsc(3).ae(0.339836909454121937) + assert acot(3).ae(0.321750554396642193) + assert asech(0.5).ae(1.31695789692481671) + assert acsch(3).ae(0.327450150237258443) + assert acoth(3).ae(0.346573590279972655) + assert acot(0).ae(1.5707963267948966192) + assert acoth(0).ae(1.5707963267948966192j) + +def test_ldexp(): + mp.dps = 15 + assert ldexp(mpf(2.5), 0) == 2.5 + assert ldexp(mpf(2.5), -1) == 1.25 + assert ldexp(mpf(2.5), 2) == 10 + assert ldexp(mpf('inf'), 3) == mpf('inf') + +def test_frexp(): + mp.dps = 15 + assert frexp(0) == (0.0, 0) + assert frexp(9) == (0.5625, 4) + assert frexp(1) == (0.5, 1) + assert frexp(0.2) == (0.8, -2) + assert frexp(1000) == (0.9765625, 10) + +def test_aliases(): + assert ln(7) == log(7) + assert log10(3.75) == log(3.75,10) + assert degrees(5.6) == 5.6 / degree + assert radians(5.6) == 5.6 * degree + assert power(-1,0.5) == j + assert fmod(25,7) == 4.0 and isinstance(fmod(25,7), mpf) + +def test_arg_sign(): + assert arg(3) == 0 + assert arg(-3).ae(pi) + assert arg(j).ae(pi/2) + assert arg(-j).ae(-pi/2) + assert arg(0) == 0 + assert isnan(atan2(3,nan)) + assert isnan(atan2(nan,3)) + assert isnan(atan2(0,nan)) + assert isnan(atan2(nan,0)) + assert isnan(atan2(nan,nan)) + assert arg(inf) == 0 + assert arg(-inf).ae(pi) + assert isnan(arg(nan)) + #assert arg(inf*j).ae(pi/2) + assert sign(0) == 0 + assert sign(3) == 1 + assert sign(-3) == -1 + assert sign(inf) == 1 + assert sign(-inf) == -1 + assert isnan(sign(nan)) + assert sign(j) == j + assert sign(-3*j) == -j + assert sign(1+j).ae((1+j)/sqrt(2)) + +def test_misc_bugs(): + # test that this doesn't raise an exception + mp.dps = 1000 + log(1302) + mp.dps = 15 + +def test_arange(): + assert arange(10) == [mpf('0.0'), mpf('1.0'), mpf('2.0'), mpf('3.0'), + mpf('4.0'), mpf('5.0'), mpf('6.0'), mpf('7.0'), + mpf('8.0'), mpf('9.0')] + assert arange(-5, 5) == [mpf('-5.0'), mpf('-4.0'), mpf('-3.0'), + mpf('-2.0'), mpf('-1.0'), mpf('0.0'), + mpf('1.0'), mpf('2.0'), mpf('3.0'), mpf('4.0')] + assert arange(0, 1, 0.1) == [mpf('0.0'), mpf('0.10000000000000001'), + mpf('0.20000000000000001'), + mpf('0.30000000000000004'), + mpf('0.40000000000000002'), + mpf('0.5'), mpf('0.60000000000000009'), + mpf('0.70000000000000007'), + mpf('0.80000000000000004'), + mpf('0.90000000000000002')] + assert arange(17, -9, -3) == [mpf('17.0'), mpf('14.0'), mpf('11.0'), + mpf('8.0'), mpf('5.0'), mpf('2.0'), + mpf('-1.0'), mpf('-4.0'), mpf('-7.0')] + assert arange(0.2, 0.1, -0.1) == [mpf('0.20000000000000001')] + assert arange(0) == [] + assert arange(1000, -1) == [] + assert arange(-1.23, 3.21, -0.0000001) == [] + +def test_linspace(): + assert linspace(2, 9, 7) == [mpf('2.0'), mpf('3.166666666666667'), + mpf('4.3333333333333339'), mpf('5.5'), mpf('6.666666666666667'), + mpf('7.8333333333333339'), mpf('9.0')] + assert linspace(2, 9, 7, endpoint=0) == [mpf('2.0'), mpf('3.0'), mpf('4.0'), + mpf('5.0'), mpf('6.0'), mpf('7.0'), mpf('8.0')] + assert linspace(2, 7, 1) == [mpf(2)] + +def test_float_cbrt(): + mp.dps = 30 + for a in arange(0,10,0.1): + assert cbrt(a*a*a).ae(a, eps) + assert cbrt(-1).ae(0.5 + j*sqrt(3)/2) + one_third = mpf(1)/3 + for a in arange(0,10,2.7) + [0.1 + 10**5]: + a = mpc(a + 1.1j) + r1 = cbrt(a) + mp.dps += 10 + r2 = pow(a, one_third) + mp.dps -= 10 + assert r1.ae(r2, eps) + mp.dps = 100 + for n in range(100, 301, 100): + w = 10**n + j*10**-3 + z = w*w*w + r = cbrt(z) + assert mpc_ae(r, w, eps) + mp.dps = 15 + +def test_root(): + mp.dps = 30 + random.seed(1) + a = random.randint(0, 10000) + p = a*a*a + r = nthroot(mpf(p), 3) + assert r == a + for n in range(4, 10): + p = p*a + assert nthroot(mpf(p), n) == a + mp.dps = 40 + for n in range(10, 5000, 100): + for a in [random.random()*10000, random.random()*10**100]: + r = nthroot(a, n) + r1 = pow(a, mpf(1)/n) + assert r.ae(r1) + r = nthroot(a, -n) + r1 = pow(a, -mpf(1)/n) + assert r.ae(r1) + # XXX: this is broken right now + # tests for nthroot rounding + for rnd in ['nearest', 'up', 'down']: + mp.rounding = rnd + for n in [-5, -3, 3, 5]: + prec = 50 + for i in range(10): + mp.prec = prec + a = rand() + mp.prec = 2*prec + b = a**n + mp.prec = prec + r = nthroot(b, n) + assert r == a + mp.dps = 30 + for n in range(3, 21): + a = (random.random() + j*random.random()) + assert nthroot(a, n).ae(pow(a, mpf(1)/n)) + assert mpc_ae(nthroot(a, n), pow(a, mpf(1)/n)) + a = (random.random()*10**100 + j*random.random()) + r = nthroot(a, n) + mp.dps += 4 + r1 = pow(a, mpf(1)/n) + mp.dps -= 4 + assert r.ae(r1) + assert mpc_ae(r, r1, eps) + r = nthroot(a, -n) + mp.dps += 4 + r1 = pow(a, -mpf(1)/n) + mp.dps -= 4 + assert r.ae(r1) + assert mpc_ae(r, r1, eps) + mp.dps = 15 + assert nthroot(4, 1) == 4 + assert nthroot(4, 0) == 1 + assert nthroot(4, -1) == 0.25 + assert nthroot(inf, 1) == inf + assert nthroot(inf, 2) == inf + assert nthroot(inf, 3) == inf + assert nthroot(inf, -1) == 0 + assert nthroot(inf, -2) == 0 + assert nthroot(inf, -3) == 0 + assert nthroot(j, 1) == j + assert nthroot(j, 0) == 1 + assert nthroot(j, -1) == -j + assert isnan(nthroot(nan, 1)) + assert isnan(nthroot(nan, 0)) + assert isnan(nthroot(nan, -1)) + assert isnan(nthroot(inf, 0)) + assert root(2,3) == nthroot(2,3) + assert root(16,4,0) == 2 + assert root(16,4,1) == 2j + assert root(16,4,2) == -2 + assert root(16,4,3) == -2j + assert root(16,4,4) == 2 + assert root(-125,3,1) == -5 + +def test_issue_136(): + for dps in [20, 80]: + mp.dps = dps + r = nthroot(mpf('-1e-20'), 4) + assert r.ae(mpf(10)**(-5) * (1 + j) * mpf(2)**(-0.5)) + mp.dps = 80 + assert nthroot('-1e-3', 4).ae(mpf(10)**(-3./4) * (1 + j)/sqrt(2)) + assert nthroot('-1e-6', 4).ae((1 + j)/(10 * sqrt(20))) + # Check that this doesn't take eternity to compute + mp.dps = 20 + assert nthroot('-1e100000000', 4).ae((1+j)*mpf('1e25000000')/sqrt(2)) + mp.dps = 15 + +def test_mpcfun_real_imag(): + mp.dps = 15 + x = mpf(0.3) + y = mpf(0.4) + assert exp(mpc(x,0)) == exp(x) + assert exp(mpc(0,y)) == mpc(cos(y),sin(y)) + assert cos(mpc(x,0)) == cos(x) + assert sin(mpc(x,0)) == sin(x) + assert cos(mpc(0,y)) == cosh(y) + assert sin(mpc(0,y)) == mpc(0,sinh(y)) + assert cospi(mpc(x,0)) == cospi(x) + assert sinpi(mpc(x,0)) == sinpi(x) + assert cospi(mpc(0,y)).ae(cosh(pi*y)) + assert sinpi(mpc(0,y)).ae(mpc(0,sinh(pi*y))) + c, s = cospi_sinpi(mpc(x,0)) + assert c == cospi(x) + assert s == sinpi(x) + c, s = cospi_sinpi(mpc(0,y)) + assert c.ae(cosh(pi*y)) + assert s.ae(mpc(0,sinh(pi*y))) + c, s = cos_sin(mpc(x,0)) + assert c == cos(x) + assert s == sin(x) + c, s = cos_sin(mpc(0,y)) + assert c == cosh(y) + assert s == mpc(0,sinh(y)) + +def test_perturbation_rounding(): + mp.dps = 100 + a = pi/10**50 + b = -pi/10**50 + c = 1 + a + d = 1 + b + mp.dps = 15 + assert exp(a) == 1 + assert exp(a, rounding='c') > 1 + assert exp(b, rounding='c') == 1 + assert exp(a, rounding='f') == 1 + assert exp(b, rounding='f') < 1 + assert cos(a) == 1 + assert cos(a, rounding='c') == 1 + assert cos(b, rounding='c') == 1 + assert cos(a, rounding='f') < 1 + assert cos(b, rounding='f') < 1 + for f in [sin, atan, asinh, tanh]: + assert f(a) == +a + assert f(a, rounding='c') > a + assert f(a, rounding='f') < a + assert f(b) == +b + assert f(b, rounding='c') > b + assert f(b, rounding='f') < b + for f in [asin, tan, sinh, atanh]: + assert f(a) == +a + assert f(b) == +b + assert f(a, rounding='c') > a + assert f(b, rounding='c') > b + assert f(a, rounding='f') < a + assert f(b, rounding='f') < b + assert ln(c) == +a + assert ln(d) == +b + assert ln(c, rounding='c') > a + assert ln(c, rounding='f') < a + assert ln(d, rounding='c') > b + assert ln(d, rounding='f') < b + assert cosh(a) == 1 + assert cosh(b) == 1 + assert cosh(a, rounding='c') > 1 + assert cosh(b, rounding='c') > 1 + assert cosh(a, rounding='f') == 1 + assert cosh(b, rounding='f') == 1 + +def test_integer_parts(): + assert floor(3.2) == 3 + assert ceil(3.2) == 4 + assert floor(3.2+5j) == 3+5j + assert ceil(3.2+5j) == 4+5j + +def test_complex_parts(): + assert fabs('3') == 3 + assert fabs(3+4j) == 5 + assert re(3) == 3 + assert re(1+4j) == 1 + assert im(3) == 0 + assert im(1+4j) == 4 + assert conj(3) == 3 + assert conj(3+4j) == 3-4j + assert mpf(3).conjugate() == 3 + +def test_cospi_sinpi(): + assert sinpi(0) == 0 + assert sinpi(0.5) == 1 + assert sinpi(1) == 0 + assert sinpi(1.5) == -1 + assert sinpi(2) == 0 + assert sinpi(2.5) == 1 + assert sinpi(-0.5) == -1 + assert cospi(0) == 1 + assert cospi(0.5) == 0 + assert cospi(1) == -1 + assert cospi(1.5) == 0 + assert cospi(2) == 1 + assert cospi(2.5) == 0 + assert cospi(-0.5) == 0 + assert cospi(100000000000.25).ae(sqrt(2)/2) + a = cospi(2+3j) + assert a.real.ae(cos((2+3j)*pi).real) + assert a.imag == 0 + b = sinpi(2+3j) + assert b.imag.ae(sin((2+3j)*pi).imag) + assert b.real == 0 + mp.dps = 35 + x1 = mpf(10000) - mpf('1e-15') + x2 = mpf(10000) + mpf('1e-15') + x3 = mpf(10000.5) - mpf('1e-15') + x4 = mpf(10000.5) + mpf('1e-15') + x5 = mpf(10001) - mpf('1e-15') + x6 = mpf(10001) + mpf('1e-15') + x7 = mpf(10001.5) - mpf('1e-15') + x8 = mpf(10001.5) + mpf('1e-15') + mp.dps = 15 + M = 10**15 + assert (sinpi(x1)*M).ae(-pi) + assert (sinpi(x2)*M).ae(pi) + assert (cospi(x3)*M).ae(pi) + assert (cospi(x4)*M).ae(-pi) + assert (sinpi(x5)*M).ae(pi) + assert (sinpi(x6)*M).ae(-pi) + assert (cospi(x7)*M).ae(-pi) + assert (cospi(x8)*M).ae(pi) + assert 0.999 < cospi(x1, rounding='d') < 1 + assert 0.999 < cospi(x2, rounding='d') < 1 + assert 0.999 < sinpi(x3, rounding='d') < 1 + assert 0.999 < sinpi(x4, rounding='d') < 1 + assert -1 < cospi(x5, rounding='d') < -0.999 + assert -1 < cospi(x6, rounding='d') < -0.999 + assert -1 < sinpi(x7, rounding='d') < -0.999 + assert -1 < sinpi(x8, rounding='d') < -0.999 + assert (sinpi(1e-15)*M).ae(pi) + assert (sinpi(-1e-15)*M).ae(-pi) + assert cospi(1e-15) == 1 + assert cospi(1e-15, rounding='d') < 1 + +def test_expj(): + assert expj(0) == 1 + assert expj(1).ae(exp(j)) + assert expj(j).ae(exp(-1)) + assert expj(1+j).ae(exp(j*(1+j))) + assert expjpi(0) == 1 + assert expjpi(1).ae(exp(j*pi)) + assert expjpi(j).ae(exp(-pi)) + assert expjpi(1+j).ae(exp(j*pi*(1+j))) + assert expjpi(-10**15 * j).ae('2.22579818340535731e+1364376353841841') + +def test_sinc(): + assert sinc(0) == sincpi(0) == 1 + assert sinc(inf) == sincpi(inf) == 0 + assert sinc(-inf) == sincpi(-inf) == 0 + assert sinc(2).ae(0.45464871341284084770) + assert sinc(2+3j).ae(0.4463290318402435457-2.7539470277436474940j) + assert sincpi(2) == 0 + assert sincpi(1.5).ae(-0.212206590789193781) + +def test_fibonacci(): + mp.dps = 15 + assert [fibonacci(n) for n in range(-5, 10)] == \ + [5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34] + assert fib(2.5).ae(1.4893065462657091) + assert fib(3+4j).ae(-5248.51130728372 - 14195.962288353j) + assert fib(1000).ae(4.3466557686937455e+208) + assert str(fib(10**100)) == '6.24499112864607e+2089876402499787337692720892375554168224592399182109535392875613974104853496745963277658556235103534' + mp.dps = 2100 + a = fib(10000) + assert a % 10**10 == 9947366875 + mp.dps = 15 + assert fibonacci(inf) == inf + assert fib(3+0j) == 2 + +def test_call_with_dps(): + mp.dps = 15 + assert abs(exp(1, dps=30)-e(dps=35)) < 1e-29 + +def test_tanh(): + mp.dps = 15 + assert tanh(0) == 0 + assert tanh(inf) == 1 + assert tanh(-inf) == -1 + assert isnan(tanh(nan)) + assert tanh(mpc('inf', '0')) == 1 + +def test_atanh(): + mp.dps = 15 + assert atanh(0) == 0 + assert atanh(0.5).ae(0.54930614433405484570) + assert atanh(-0.5).ae(-0.54930614433405484570) + assert atanh(1) == inf + assert atanh(-1) == -inf + assert isnan(atanh(nan)) + assert isinstance(atanh(1), mpf) + assert isinstance(atanh(-1), mpf) + # Limits at infinity + jpi2 = j*pi/2 + assert atanh(inf).ae(-jpi2) + assert atanh(-inf).ae(jpi2) + assert atanh(mpc(inf,-1)).ae(-jpi2) + assert atanh(mpc(inf,0)).ae(-jpi2) + assert atanh(mpc(inf,1)).ae(jpi2) + assert atanh(mpc(1,inf)).ae(jpi2) + assert atanh(mpc(0,inf)).ae(jpi2) + assert atanh(mpc(-1,inf)).ae(jpi2) + assert atanh(mpc(-inf,1)).ae(jpi2) + assert atanh(mpc(-inf,0)).ae(jpi2) + assert atanh(mpc(-inf,-1)).ae(-jpi2) + assert atanh(mpc(-1,-inf)).ae(-jpi2) + assert atanh(mpc(0,-inf)).ae(-jpi2) + assert atanh(mpc(1,-inf)).ae(-jpi2) + +def test_expm1(): + mp.dps = 15 + assert expm1(0) == 0 + assert expm1(3).ae(exp(3)-1) + assert expm1(inf) == inf + assert expm1(1e-50).ae(1e-50) + assert (expm1(1e-10)*1e10).ae(1.00000000005) + +def test_log1p(): + mp.dps = 15 + assert log1p(0) == 0 + assert log1p(3).ae(log(1+3)) + assert log1p(inf) == inf + assert log1p(1e-50).ae(1e-50) + assert (log1p(1e-10)*1e10).ae(0.99999999995) + +def test_powm1(): + mp.dps = 15 + assert powm1(2,3) == 7 + assert powm1(-1,2) == 0 + assert powm1(-1,0) == 0 + assert powm1(-2,0) == 0 + assert powm1(3+4j,0) == 0 + assert powm1(0,1) == -1 + assert powm1(0,0) == 0 + assert powm1(1,0) == 0 + assert powm1(1,2) == 0 + assert powm1(1,3+4j) == 0 + assert powm1(1,5) == 0 + assert powm1(j,4) == 0 + assert powm1(-j,4) == 0 + assert (powm1(2,1e-100)*1e100).ae(ln2) + assert powm1(2,'1e-100000000000') != 0 + assert (powm1(fadd(1,1e-100,exact=True), 5)*1e100).ae(5) + +def test_unitroots(): + assert unitroots(1) == [1] + assert unitroots(2) == [1, -1] + a, b, c = unitroots(3) + assert a == 1 + assert b.ae(-0.5 + 0.86602540378443864676j) + assert c.ae(-0.5 - 0.86602540378443864676j) + assert unitroots(1, primitive=True) == [1] + assert unitroots(2, primitive=True) == [-1] + assert unitroots(3, primitive=True) == unitroots(3)[1:] + assert unitroots(4, primitive=True) == [j, -j] + assert len(unitroots(17, primitive=True)) == 16 + assert len(unitroots(16, primitive=True)) == 8 + +def test_cyclotomic(): + mp.dps = 15 + assert [cyclotomic(n,1) for n in range(31)] == [1,0,2,3,2,5,1,7,2,3,1,11,1,13,1,1,2,17,1,19,1,1,1,23,1,5,1,3,1,29,1] + assert [cyclotomic(n,-1) for n in range(31)] == [1,-2,0,1,2,1,3,1,2,1,5,1,1,1,7,1,2,1,3,1,1,1,11,1,1,1,13,1,1,1,1] + assert [cyclotomic(n,j) for n in range(21)] == [1,-1+j,1+j,j,0,1,-j,j,2,-j,1,j,3,1,-j,1,2,1,j,j,5] + assert [cyclotomic(n,-j) for n in range(21)] == [1,-1-j,1-j,-j,0,1,j,-j,2,j,1,-j,3,1,j,1,2,1,-j,-j,5] + assert cyclotomic(1624,j) == 1 + assert cyclotomic(33600,j) == 1 + u = sqrt(j, prec=500) + assert cyclotomic(8, u).ae(0) + assert cyclotomic(30, u).ae(5.8284271247461900976) + assert cyclotomic(2040, u).ae(1) + assert cyclotomic(0,2.5) == 1 + assert cyclotomic(1,2.5) == 2.5-1 + assert cyclotomic(2,2.5) == 2.5+1 + assert cyclotomic(3,2.5) == 2.5**2 + 2.5 + 1 + assert cyclotomic(7,2.5) == 406.234375 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions2.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions2.py new file mode 100644 index 0000000000000000000000000000000000000000..2b2d57fcec9be0db4d921b013f24fd6a5e0e9930 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_functions2.py @@ -0,0 +1,2384 @@ +import math +import pytest +from mpmath import * + +def test_bessel(): + mp.dps = 15 + assert j0(1).ae(0.765197686557966551) + assert j0(pi).ae(-0.304242177644093864) + assert j0(1000).ae(0.0247866861524201746) + assert j0(-25).ae(0.0962667832759581162) + assert j1(1).ae(0.440050585744933516) + assert j1(pi).ae(0.284615343179752757) + assert j1(1000).ae(0.00472831190708952392) + assert j1(-25).ae(0.125350249580289905) + assert besselj(5,1).ae(0.000249757730211234431) + assert besselj(5+0j,1).ae(0.000249757730211234431) + assert besselj(5,pi).ae(0.0521411843671184747) + assert besselj(5,1000).ae(0.00502540694523318607) + assert besselj(5,-25).ae(0.0660079953984229934) + assert besselj(-3,2).ae(-0.128943249474402051) + assert besselj(-4,2).ae(0.0339957198075684341) + assert besselj(3,3+2j).ae(0.424718794929639595942 + 0.625665327745785804812j) + assert besselj(0.25,4).ae(-0.374760630804249715) + assert besselj(1+2j,3+4j).ae(0.319247428741872131 - 0.669557748880365678j) + assert (besselj(3, 10**10) * 10**5).ae(0.76765081748139204023) + assert bessely(-0.5, 0) == 0 + assert bessely(0.5, 0) == -inf + assert bessely(1.5, 0) == -inf + assert bessely(0,0) == -inf + assert bessely(-0.4, 0) == -inf + assert bessely(-0.6, 0) == inf + assert bessely(-1, 0) == inf + assert bessely(-1.4, 0) == inf + assert bessely(-1.6, 0) == -inf + assert bessely(-1, 0) == inf + assert bessely(-2, 0) == -inf + assert bessely(-3, 0) == inf + assert bessely(0.5, 0) == -inf + assert bessely(1, 0) == -inf + assert bessely(1.5, 0) == -inf + assert bessely(2, 0) == -inf + assert bessely(2.5, 0) == -inf + assert bessely(3, 0) == -inf + assert bessely(0,0.5).ae(-0.44451873350670655715) + assert bessely(1,0.5).ae(-1.4714723926702430692) + assert bessely(-1,0.5).ae(1.4714723926702430692) + assert bessely(3.5,0.5).ae(-138.86400867242488443) + assert bessely(0,3+4j).ae(4.6047596915010138655-8.8110771408232264208j) + assert bessely(0,j).ae(-0.26803248203398854876+1.26606587775200833560j) + assert (bessely(3, 10**10) * 10**5).ae(0.21755917537013204058) + assert besseli(0,0) == 1 + assert besseli(1,0) == 0 + assert besseli(2,0) == 0 + assert besseli(-1,0) == 0 + assert besseli(-2,0) == 0 + assert besseli(0,0.5).ae(1.0634833707413235193) + assert besseli(1,0.5).ae(0.25789430539089631636) + assert besseli(-1,0.5).ae(0.25789430539089631636) + assert besseli(3.5,0.5).ae(0.00068103597085793815863) + assert besseli(0,3+4j).ae(-3.3924877882755196097-1.3239458916287264815j) + assert besseli(0,j).ae(besselj(0,1)) + assert (besseli(3, 10**10) * mpf(10)**(-4342944813)).ae(4.2996028505491271875) + assert besselk(0,0) == inf + assert besselk(1,0) == inf + assert besselk(2,0) == inf + assert besselk(-1,0) == inf + assert besselk(-2,0) == inf + assert besselk(0,0.5).ae(0.92441907122766586178) + assert besselk(1,0.5).ae(1.6564411200033008937) + assert besselk(-1,0.5).ae(1.6564411200033008937) + assert besselk(3.5,0.5).ae(207.48418747548460607) + assert besselk(0,3+4j).ae(-0.007239051213570155013+0.026510418350267677215j) + assert besselk(0,j).ae(-0.13863371520405399968-1.20196971531720649914j) + assert (besselk(3, 10**10) * mpf(10)**4342944824).ae(1.1628981033356187851) + # test for issue 331, bug reported by Michael Hartmann + for n in range(10,100,10): + mp.dps = n + assert besseli(91.5,24.7708).ae("4.00830632138673963619656140653537080438462342928377020695738635559218797348548092636896796324190271316137982810144874264e-41") + +def test_bessel_zeros(): + mp.dps = 15 + assert besseljzero(0,1).ae(2.40482555769577276869) + assert besseljzero(2,1).ae(5.1356223018406825563) + assert besseljzero(1,50).ae(157.86265540193029781) + assert besseljzero(10,1).ae(14.475500686554541220) + assert besseljzero(0.5,3).ae(9.4247779607693797153) + assert besseljzero(2,1,1).ae(3.0542369282271403228) + assert besselyzero(0,1).ae(0.89357696627916752158) + assert besselyzero(2,1).ae(3.3842417671495934727) + assert besselyzero(1,50).ae(156.29183520147840108) + assert besselyzero(10,1).ae(12.128927704415439387) + assert besselyzero(0.5,3).ae(7.8539816339744830962) + assert besselyzero(2,1,1).ae(5.0025829314460639452) + +def test_hankel(): + mp.dps = 15 + assert hankel1(0,0.5).ae(0.93846980724081290423-0.44451873350670655715j) + assert hankel1(1,0.5).ae(0.2422684576748738864-1.4714723926702430692j) + assert hankel1(-1,0.5).ae(-0.2422684576748738864+1.4714723926702430692j) + assert hankel1(1.5,0.5).ae(0.0917016996256513026-2.5214655504213378514j) + assert hankel1(1.5,3+4j).ae(0.0066806866476728165382-0.0036684231610839127106j) + assert hankel2(0,0.5).ae(0.93846980724081290423+0.44451873350670655715j) + assert hankel2(1,0.5).ae(0.2422684576748738864+1.4714723926702430692j) + assert hankel2(-1,0.5).ae(-0.2422684576748738864-1.4714723926702430692j) + assert hankel2(1.5,0.5).ae(0.0917016996256513026+2.5214655504213378514j) + assert hankel2(1.5,3+4j).ae(14.783528526098567526-7.397390270853446512j) + +def test_struve(): + mp.dps = 15 + assert struveh(2,3).ae(0.74238666967748318564) + assert struveh(-2.5,3).ae(0.41271003220971599344) + assert struvel(2,3).ae(1.7476573277362782744) + assert struvel(-2.5,3).ae(1.5153394466819651377) + +def test_whittaker(): + mp.dps = 15 + assert whitm(2,3,4).ae(49.753745589025246591) + assert whitw(2,3,4).ae(14.111656223052932215) + +def test_kelvin(): + mp.dps = 15 + assert ber(2,3).ae(0.80836846563726819091) + assert ber(3,4).ae(-0.28262680167242600233) + assert ber(-3,2).ae(-0.085611448496796363669) + assert bei(2,3).ae(-0.89102236377977331571) + assert bei(-3,2).ae(-0.14420994155731828415) + assert ker(2,3).ae(0.12839126695733458928) + assert ker(-3,2).ae(-0.29802153400559142783) + assert ker(0.5,3).ae(-0.085662378535217097524) + assert kei(2,3).ae(0.036804426134164634000) + assert kei(-3,2).ae(0.88682069845786731114) + assert kei(0.5,3).ae(0.013633041571314302948) + +def test_hyper_misc(): + mp.dps = 15 + assert hyp0f1(1,0) == 1 + assert hyp1f1(1,2,0) == 1 + assert hyp1f2(1,2,3,0) == 1 + assert hyp2f1(1,2,3,0) == 1 + assert hyp2f2(1,2,3,4,0) == 1 + assert hyp2f3(1,2,3,4,5,0) == 1 + # Degenerate case: 0F0 + assert hyper([],[],0) == 1 + assert hyper([],[],-2).ae(exp(-2)) + # Degenerate case: 1F0 + assert hyper([2],[],1.5) == 4 + # + assert hyp2f1((1,3),(2,3),(5,6),mpf(27)/32).ae(1.6) + assert hyp2f1((1,4),(1,2),(3,4),mpf(80)/81).ae(1.8) + assert hyp2f1((2,3),(1,1),(3,2),(2+j)/3).ae(1.327531603558679093+0.439585080092769253j) + mp.dps = 25 + v = mpc('1.2282306665029814734863026', '-0.1225033830118305184672133') + assert hyper([(3,4),2+j,1],[1,5,j/3],mpf(1)/5+j/8).ae(v) + mp.dps = 15 + +def test_elliptic_integrals(): + mp.dps = 15 + assert ellipk(0).ae(pi/2) + assert ellipk(0.5).ae(gamma(0.25)**2/(4*sqrt(pi))) + assert ellipk(1) == inf + assert ellipk(1+0j) == inf + assert ellipk(-1).ae('1.3110287771460599052') + assert ellipk(-2).ae('1.1714200841467698589') + assert isinstance(ellipk(-2), mpf) + assert isinstance(ellipe(-2), mpf) + assert ellipk(-50).ae('0.47103424540873331679') + mp.dps = 30 + n1 = +fraction(99999,100000) + n2 = +fraction(100001,100000) + mp.dps = 15 + assert ellipk(n1).ae('7.1427724505817781901') + assert ellipk(n2).ae(mpc('7.1427417367963090109', '-1.5707923998261688019')) + assert ellipe(n1).ae('1.0000332138990829170') + v = ellipe(n2) + assert v.real.ae('0.999966786328145474069137') + assert (v.imag*10**6).ae('7.853952181727432') + assert ellipk(2).ae(mpc('1.3110287771460599052', '-1.3110287771460599052')) + assert ellipk(50).ae(mpc('0.22326753950210985451', '-0.47434723226254522087')) + assert ellipk(3+4j).ae(mpc('0.91119556380496500866', '0.63133428324134524388')) + assert ellipk(3-4j).ae(mpc('0.91119556380496500866', '-0.63133428324134524388')) + assert ellipk(-3+4j).ae(mpc('0.95357894880405122483', '0.23093044503746114444')) + assert ellipk(-3-4j).ae(mpc('0.95357894880405122483', '-0.23093044503746114444')) + assert isnan(ellipk(nan)) + assert isnan(ellipe(nan)) + assert ellipk(inf) == 0 + assert isinstance(ellipk(inf), mpc) + assert ellipk(-inf) == 0 + assert ellipk(1+0j) == inf + assert ellipe(0).ae(pi/2) + assert ellipe(0.5).ae(pi**(mpf(3)/2)/gamma(0.25)**2 +gamma(0.25)**2/(8*sqrt(pi))) + assert ellipe(1) == 1 + assert ellipe(1+0j) == 1 + assert ellipe(inf) == mpc(0,inf) + assert ellipe(-inf) == inf + assert ellipe(3+4j).ae(1.4995535209333469543-1.5778790079127582745j) + assert ellipe(3-4j).ae(1.4995535209333469543+1.5778790079127582745j) + assert ellipe(-3+4j).ae(2.5804237855343377803-0.8306096791000413778j) + assert ellipe(-3-4j).ae(2.5804237855343377803+0.8306096791000413778j) + assert ellipe(2).ae(0.59907011736779610372+0.59907011736779610372j) + assert ellipe('1e-1000000000').ae(pi/2) + assert ellipk('1e-1000000000').ae(pi/2) + assert ellipe(-pi).ae(2.4535865983838923) + mp.dps = 50 + assert ellipk(1/pi).ae('1.724756270009501831744438120951614673874904182624739673') + assert ellipe(1/pi).ae('1.437129808135123030101542922290970050337425479058225712') + assert ellipk(-10*pi).ae('0.5519067523886233967683646782286965823151896970015484512') + assert ellipe(-10*pi).ae('5.926192483740483797854383268707108012328213431657645509') + v = ellipk(pi) + assert v.real.ae('0.973089521698042334840454592642137667227167622330325225') + assert v.imag.ae('-1.156151296372835303836814390793087600271609993858798016') + v = ellipe(pi) + assert v.real.ae('0.4632848917264710404078033487934663562998345622611263332') + assert v.imag.ae('1.0637961621753130852473300451583414489944099504180510966') + mp.dps = 15 + +def test_exp_integrals(): + mp.dps = 15 + x = +e + z = e + sqrt(3)*j + assert ei(x).ae(8.21168165538361560) + assert li(x).ae(1.89511781635593676) + assert si(x).ae(1.82104026914756705) + assert ci(x).ae(0.213958001340379779) + assert shi(x).ae(4.11520706247846193) + assert chi(x).ae(4.09647459290515367) + assert fresnels(x).ae(0.437189718149787643) + assert fresnelc(x).ae(0.401777759590243012) + assert airyai(x).ae(0.0108502401568586681) + assert airybi(x).ae(8.98245748585468627) + assert ei(z).ae(3.72597969491314951 + 7.34213212314224421j) + assert li(z).ae(2.28662658112562502 + 1.50427225297269364j) + assert si(z).ae(2.48122029237669054 + 0.12684703275254834j) + assert ci(z).ae(0.169255590269456633 - 0.892020751420780353j) + assert shi(z).ae(1.85810366559344468 + 3.66435842914920263j) + assert chi(z).ae(1.86787602931970484 + 3.67777369399304159j) + assert fresnels(z/3).ae(0.034534397197008182 + 0.754859844188218737j) + assert fresnelc(z/3).ae(1.261581645990027372 + 0.417949198775061893j) + assert airyai(z).ae(-0.0162552579839056062 - 0.0018045715700210556j) + assert airybi(z).ae(-4.98856113282883371 + 2.08558537872180623j) + assert li(0) == 0.0 + assert li(1) == -inf + assert li(inf) == inf + assert isinstance(li(0.7), mpf) + assert si(inf).ae(pi/2) + assert si(-inf).ae(-pi/2) + assert ci(inf) == 0 + assert ci(0) == -inf + assert isinstance(ei(-0.7), mpf) + assert airyai(inf) == 0 + assert airybi(inf) == inf + assert airyai(-inf) == 0 + assert airybi(-inf) == 0 + assert fresnels(inf) == 0.5 + assert fresnelc(inf) == 0.5 + assert fresnels(-inf) == -0.5 + assert fresnelc(-inf) == -0.5 + assert shi(0) == 0 + assert shi(inf) == inf + assert shi(-inf) == -inf + assert chi(0) == -inf + assert chi(inf) == inf + +def test_ei(): + mp.dps = 15 + assert ei(0) == -inf + assert ei(inf) == inf + assert ei(-inf) == -0.0 + assert ei(20+70j).ae(6.1041351911152984397e6 - 2.7324109310519928872e6j) + # tests for the asymptotic expansion + # values checked with Mathematica ExpIntegralEi + mp.dps = 50 + r = ei(20000) + s = '3.8781962825045010930273870085501819470698476975019e+8681' + assert str(r) == s + r = ei(-200) + s = '-6.8852261063076355977108174824557929738368086933303e-90' + assert str(r) == s + r =ei(20000 + 10*j) + sre = '-3.255138234032069402493850638874410725961401274106e+8681' + sim = '-2.1081929993474403520785942429469187647767369645423e+8681' + assert str(r.real) == sre and str(r.imag) == sim + mp.dps = 15 + # More asymptotic expansions + assert chi(-10**6+100j).ae('1.3077239389562548386e+434288 + 7.6808956999707408158e+434287j') + assert shi(-10**6+100j).ae('-1.3077239389562548386e+434288 - 7.6808956999707408158e+434287j') + mp.dps = 15 + assert ei(10j).ae(-0.0454564330044553726+3.2291439210137706686j) + assert ei(100j).ae(-0.0051488251426104921+3.1330217936839529126j) + u = ei(fmul(10**20, j, exact=True)) + assert u.real.ae(-6.4525128526578084421345e-21, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(pi) + assert ei(-10j).ae(-0.0454564330044553726-3.2291439210137706686j) + assert ei(-100j).ae(-0.0051488251426104921-3.1330217936839529126j) + u = ei(fmul(-10**20, j, exact=True)) + assert u.real.ae(-6.4525128526578084421345e-21, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(-pi) + assert ei(10+10j).ae(-1576.1504265768517448+436.9192317011328140j) + u = ei(-10+10j) + assert u.real.ae(7.6698978415553488362543e-7, abs_eps=0, rel_eps=8*eps) + assert u.imag.ae(3.141595611735621062025) + +def test_e1(): + mp.dps = 15 + assert e1(0) == inf + assert e1(inf) == 0 + assert e1(-inf) == mpc(-inf, -pi) + assert e1(10j).ae(0.045456433004455372635 + 0.087551267423977430100j) + assert e1(100j).ae(0.0051488251426104921444 - 0.0085708599058403258790j) + assert e1(fmul(10**20, j, exact=True)).ae(6.4525128526578084421e-21 - 7.6397040444172830039e-21j, abs_eps=0, rel_eps=8*eps) + assert e1(-10j).ae(0.045456433004455372635 - 0.087551267423977430100j) + assert e1(-100j).ae(0.0051488251426104921444 + 0.0085708599058403258790j) + assert e1(fmul(-10**20, j, exact=True)).ae(6.4525128526578084421e-21 + 7.6397040444172830039e-21j, abs_eps=0, rel_eps=8*eps) + +def test_expint(): + mp.dps = 15 + assert expint(0,0) == inf + assert expint(0,1).ae(1/e) + assert expint(0,1.5).ae(2/exp(1.5)/3) + assert expint(1,1).ae(-ei(-1)) + assert expint(2,0).ae(1) + assert expint(3,0).ae(1/2.) + assert expint(4,0).ae(1/3.) + assert expint(-2, 0.5).ae(26/sqrt(e)) + assert expint(-1,-1) == 0 + assert expint(-2,-1).ae(-e) + assert expint(5.5, 0).ae(2/9.) + assert expint(2.00000001,0).ae(100000000./100000001) + assert expint(2+3j,4-j).ae(0.0023461179581675065414+0.0020395540604713669262j) + assert expint('1.01', '1e-1000').ae(99.9999999899412802) + assert expint('1.000000000001', 3.5).ae(0.00697013985754701819446) + assert expint(2,3).ae(3*ei(-3)+exp(-3)) + assert (expint(10,20)*10**10).ae(0.694439055541231353) + assert expint(3,inf) == 0 + assert expint(3.2,inf) == 0 + assert expint(3.2+2j,inf) == 0 + assert expint(1,3j).ae(-0.11962978600800032763 + 0.27785620120457163717j) + assert expint(1,3).ae(0.013048381094197037413) + assert expint(1,-3).ae(-ei(3)-pi*j) + #assert expint(3) == expint(1,3) + assert expint(1,-20).ae(-25615652.66405658882 - 3.1415926535897932385j) + assert expint(1000000,0).ae(1./999999) + assert expint(0,2+3j).ae(-0.025019798357114678171 + 0.027980439405104419040j) + assert expint(-1,2+3j).ae(-0.022411973626262070419 + 0.038058922011377716932j) + assert expint(-1.5,0) == inf + +def test_trig_integrals(): + mp.dps = 30 + assert si(mpf(1)/1000000).ae('0.000000999999999999944444444444446111') + assert ci(mpf(1)/1000000).ae('-13.2382948930629912435014366276') + assert si(10**10).ae('1.5707963267075846569685111517747537') + assert ci(10**10).ae('-4.87506025174822653785729773959e-11') + assert si(10**100).ae(pi/2) + assert (ci(10**100)*10**100).ae('-0.372376123661276688262086695553') + assert si(-3) == -si(3) + assert ci(-3).ae(ci(3) + pi*j) + # Test complex structure + mp.dps = 15 + assert mp.ci(50).ae(-0.0056283863241163054402) + assert mp.ci(50+2j).ae(-0.018378282946133067149+0.070352808023688336193j) + assert mp.ci(20j).ae(1.28078263320282943611e7+1.5707963267949j) + assert mp.ci(-2+20j).ae(-4.050116856873293505e6+1.207476188206989909e7j) + assert mp.ci(-50+2j).ae(-0.0183782829461330671+3.0712398455661049023j) + assert mp.ci(-50).ae(-0.0056283863241163054+3.1415926535897932385j) + assert mp.ci(-50-2j).ae(-0.0183782829461330671-3.0712398455661049023j) + assert mp.ci(-2-20j).ae(-4.050116856873293505e6-1.207476188206989909e7j) + assert mp.ci(-20j).ae(1.28078263320282943611e7-1.5707963267949j) + assert mp.ci(50-2j).ae(-0.018378282946133067149-0.070352808023688336193j) + assert mp.si(50).ae(1.5516170724859358947) + assert mp.si(50+2j).ae(1.497884414277228461-0.017515007378437448j) + assert mp.si(20j).ae(1.2807826332028294459e7j) + assert mp.si(-2+20j).ae(-1.20747603112735722103e7-4.050116856873293554e6j) + assert mp.si(-50+2j).ae(-1.497884414277228461-0.017515007378437448j) + assert mp.si(-50).ae(-1.5516170724859358947) + assert mp.si(-50-2j).ae(-1.497884414277228461+0.017515007378437448j) + assert mp.si(-2-20j).ae(-1.20747603112735722103e7+4.050116856873293554e6j) + assert mp.si(-20j).ae(-1.2807826332028294459e7j) + assert mp.si(50-2j).ae(1.497884414277228461+0.017515007378437448j) + assert mp.chi(50j).ae(-0.0056283863241163054+1.5707963267948966192j) + assert mp.chi(-2+50j).ae(-0.0183782829461330671+1.6411491348185849554j) + assert mp.chi(-20).ae(1.28078263320282943611e7+3.1415926535898j) + assert mp.chi(-20-2j).ae(-4.050116856873293505e6+1.20747571696809187053e7j) + assert mp.chi(-2-50j).ae(-0.0183782829461330671-1.6411491348185849554j) + assert mp.chi(-50j).ae(-0.0056283863241163054-1.5707963267948966192j) + assert mp.chi(2-50j).ae(-0.0183782829461330671-1.500443518771208283j) + assert mp.chi(20-2j).ae(-4.050116856873293505e6-1.20747603112735722951e7j) + assert mp.chi(20).ae(1.2807826332028294361e7) + assert mp.chi(2+50j).ae(-0.0183782829461330671+1.500443518771208283j) + assert mp.shi(50j).ae(1.5516170724859358947j) + assert mp.shi(-2+50j).ae(0.017515007378437448+1.497884414277228461j) + assert mp.shi(-20).ae(-1.2807826332028294459e7) + assert mp.shi(-20-2j).ae(4.050116856873293554e6-1.20747603112735722103e7j) + assert mp.shi(-2-50j).ae(0.017515007378437448-1.497884414277228461j) + assert mp.shi(-50j).ae(-1.5516170724859358947j) + assert mp.shi(2-50j).ae(-0.017515007378437448-1.497884414277228461j) + assert mp.shi(20-2j).ae(-4.050116856873293554e6-1.20747603112735722103e7j) + assert mp.shi(20).ae(1.2807826332028294459e7) + assert mp.shi(2+50j).ae(-0.017515007378437448+1.497884414277228461j) + def ae(x,y,tol=1e-12): + return abs(x-y) <= abs(y)*tol + assert fp.ci(fp.inf) == 0 + assert ae(fp.ci(fp.ninf), fp.pi*1j) + assert ae(fp.si(fp.inf), fp.pi/2) + assert ae(fp.si(fp.ninf), -fp.pi/2) + assert fp.si(0) == 0 + assert ae(fp.ci(50), -0.0056283863241163054402) + assert ae(fp.ci(50+2j), -0.018378282946133067149+0.070352808023688336193j) + assert ae(fp.ci(20j), 1.28078263320282943611e7+1.5707963267949j) + assert ae(fp.ci(-2+20j), -4.050116856873293505e6+1.207476188206989909e7j) + assert ae(fp.ci(-50+2j), -0.0183782829461330671+3.0712398455661049023j) + assert ae(fp.ci(-50), -0.0056283863241163054+3.1415926535897932385j) + assert ae(fp.ci(-50-2j), -0.0183782829461330671-3.0712398455661049023j) + assert ae(fp.ci(-2-20j), -4.050116856873293505e6-1.207476188206989909e7j) + assert ae(fp.ci(-20j), 1.28078263320282943611e7-1.5707963267949j) + assert ae(fp.ci(50-2j), -0.018378282946133067149-0.070352808023688336193j) + assert ae(fp.si(50), 1.5516170724859358947) + assert ae(fp.si(50+2j), 1.497884414277228461-0.017515007378437448j) + assert ae(fp.si(20j), 1.2807826332028294459e7j) + assert ae(fp.si(-2+20j), -1.20747603112735722103e7-4.050116856873293554e6j) + assert ae(fp.si(-50+2j), -1.497884414277228461-0.017515007378437448j) + assert ae(fp.si(-50), -1.5516170724859358947) + assert ae(fp.si(-50-2j), -1.497884414277228461+0.017515007378437448j) + assert ae(fp.si(-2-20j), -1.20747603112735722103e7+4.050116856873293554e6j) + assert ae(fp.si(-20j), -1.2807826332028294459e7j) + assert ae(fp.si(50-2j), 1.497884414277228461+0.017515007378437448j) + assert ae(fp.chi(50j), -0.0056283863241163054+1.5707963267948966192j) + assert ae(fp.chi(-2+50j), -0.0183782829461330671+1.6411491348185849554j) + assert ae(fp.chi(-20), 1.28078263320282943611e7+3.1415926535898j) + assert ae(fp.chi(-20-2j), -4.050116856873293505e6+1.20747571696809187053e7j) + assert ae(fp.chi(-2-50j), -0.0183782829461330671-1.6411491348185849554j) + assert ae(fp.chi(-50j), -0.0056283863241163054-1.5707963267948966192j) + assert ae(fp.chi(2-50j), -0.0183782829461330671-1.500443518771208283j) + assert ae(fp.chi(20-2j), -4.050116856873293505e6-1.20747603112735722951e7j) + assert ae(fp.chi(20), 1.2807826332028294361e7) + assert ae(fp.chi(2+50j), -0.0183782829461330671+1.500443518771208283j) + assert ae(fp.shi(50j), 1.5516170724859358947j) + assert ae(fp.shi(-2+50j), 0.017515007378437448+1.497884414277228461j) + assert ae(fp.shi(-20), -1.2807826332028294459e7) + assert ae(fp.shi(-20-2j), 4.050116856873293554e6-1.20747603112735722103e7j) + assert ae(fp.shi(-2-50j), 0.017515007378437448-1.497884414277228461j) + assert ae(fp.shi(-50j), -1.5516170724859358947j) + assert ae(fp.shi(2-50j), -0.017515007378437448-1.497884414277228461j) + assert ae(fp.shi(20-2j), -4.050116856873293554e6-1.20747603112735722103e7j) + assert ae(fp.shi(20), 1.2807826332028294459e7) + assert ae(fp.shi(2+50j), -0.017515007378437448+1.497884414277228461j) + +def test_airy(): + mp.dps = 15 + assert (airyai(10)*10**10).ae(1.1047532552898687) + assert (airybi(10)/10**9).ae(0.45564115354822515) + assert (airyai(1000)*10**9158).ae(9.306933063179556004) + assert (airybi(1000)/10**9154).ae(5.4077118391949465477) + assert airyai(-1000).ae(0.055971895773019918842) + assert airybi(-1000).ae(-0.083264574117080633012) + assert (airyai(100+100j)*10**188).ae(2.9099582462207032076 + 2.353013591706178756j) + assert (airybi(100+100j)/10**185).ae(1.7086751714463652039 - 3.1416590020830804578j) + +def test_hyper_0f1(): + mp.dps = 15 + v = 8.63911136507950465 + assert hyper([],[(1,3)],1.5).ae(v) + assert hyper([],[1/3.],1.5).ae(v) + assert hyp0f1(1/3.,1.5).ae(v) + assert hyp0f1((1,3),1.5).ae(v) + # Asymptotic expansion + assert hyp0f1(3,1e9).ae('4.9679055380347771271e+27455') + assert hyp0f1(3,1e9j).ae('-2.1222788784457702157e+19410 + 5.0840597555401854116e+19410j') + +def test_hyper_1f1(): + mp.dps = 15 + v = 1.2917526488617656673 + assert hyper([(1,2)],[(3,2)],0.7).ae(v) + assert hyper([(1,2)],[(3,2)],0.7+0j).ae(v) + assert hyper([0.5],[(3,2)],0.7).ae(v) + assert hyper([0.5],[1.5],0.7).ae(v) + assert hyper([0.5],[(3,2)],0.7+0j).ae(v) + assert hyper([0.5],[1.5],0.7+0j).ae(v) + assert hyper([(1,2)],[1.5+0j],0.7).ae(v) + assert hyper([0.5+0j],[1.5],0.7).ae(v) + assert hyper([0.5+0j],[1.5+0j],0.7+0j).ae(v) + assert hyp1f1(0.5,1.5,0.7).ae(v) + assert hyp1f1((1,2),1.5,0.7).ae(v) + # Asymptotic expansion + assert hyp1f1(2,3,1e10).ae('2.1555012157015796988e+4342944809') + assert (hyp1f1(2,3,1e10j)*10**10).ae(-0.97501205020039745852 - 1.7462392454512132074j) + # Shouldn't use asymptotic expansion + assert hyp1f1(-2, 1, 10000).ae(49980001) + # Bug + assert hyp1f1(1j,fraction(1,3),0.415-69.739j).ae(25.857588206024346592 + 15.738060264515292063j) + +def test_hyper_2f1(): + mp.dps = 15 + v = 1.0652207633823291032 + assert hyper([(1,2), (3,4)], [2], 0.3).ae(v) + assert hyper([(1,2), 0.75], [2], 0.3).ae(v) + assert hyper([0.5, 0.75], [2.0], 0.3).ae(v) + assert hyper([0.5, 0.75], [2.0], 0.3+0j).ae(v) + assert hyper([0.5+0j, (3,4)], [2.0], 0.3+0j).ae(v) + assert hyper([0.5+0j, (3,4)], [2.0], 0.3).ae(v) + assert hyper([0.5, (3,4)], [2.0+0j], 0.3).ae(v) + assert hyper([0.5+0j, 0.75+0j], [2.0+0j], 0.3+0j).ae(v) + v = 1.09234681096223231717 + 0.18104859169479360380j + assert hyper([(1,2),0.75+j], [2], 0.5).ae(v) + assert hyper([0.5,0.75+j], [2.0], 0.5).ae(v) + assert hyper([0.5,0.75+j], [2.0], 0.5+0j).ae(v) + assert hyper([0.5,0.75+j], [2.0+0j], 0.5+0j).ae(v) + v = 0.9625 - 0.125j + assert hyper([(3,2),-1],[4], 0.1+j/3).ae(v) + assert hyper([1.5,-1.0],[4], 0.1+j/3).ae(v) + assert hyper([1.5,-1.0],[4+0j], 0.1+j/3).ae(v) + assert hyper([1.5+0j,-1.0+0j],[4+0j], 0.1+j/3).ae(v) + v = 1.02111069501693445001 - 0.50402252613466859521j + assert hyper([(2,10),(3,10)],[(4,10)],1.5).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],1.5).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],1.5+0j).ae(v) + v = 0.76922501362865848528 + 0.32640579593235886194j + assert hyper([(2,10),(3,10)],[(4,10)],4+2j).ae(v) + assert hyper([0.2,(3,10)],[0.4+0j],4+2j).ae(v) + assert hyper([0.2,(3,10)],[(4,10)],4+2j).ae(v) + +def test_hyper_2f1_hard(): + mp.dps = 15 + # Singular cases + assert hyp2f1(2,-1,-1,3).ae(7) + assert hyp2f1(2,-1,-1,3,eliminate_all=True).ae(0.25) + assert hyp2f1(2,-2,-2,3).ae(34) + assert hyp2f1(2,-2,-2,3,eliminate_all=True).ae(0.25) + assert hyp2f1(2,-2,-3,3) == 14 + assert hyp2f1(2,-3,-2,3) == inf + assert hyp2f1(2,-1.5,-1.5,3) == 0.25 + assert hyp2f1(1,2,3,0) == 1 + assert hyp2f1(0,1,0,0) == 1 + assert hyp2f1(0,0,0,0) == 1 + assert isnan(hyp2f1(1,1,0,0)) + assert hyp2f1(2,-1,-5, 0.25+0.25j).ae(1.1+0.1j) + assert hyp2f1(2,-5,-5, 0.25+0.25j, eliminate=False).ae(163./128 + 125./128*j) + assert hyp2f1(0.7235, -1, -5, 0.3).ae(1.04341) + assert hyp2f1(0.7235, -5, -5, 0.3, eliminate=False).ae(1.2939225017815903812) + assert hyp2f1(-1,-2,4,1) == 1.5 + assert hyp2f1(1,2,-3,1) == inf + assert hyp2f1(-2,-2,1,1) == 6 + assert hyp2f1(1,-2,-4,1).ae(5./3) + assert hyp2f1(0,-6,-4,1) == 1 + assert hyp2f1(0,-3,-4,1) == 1 + assert hyp2f1(0,0,0,1) == 1 + assert hyp2f1(1,0,0,1,eliminate=False) == 1 + assert hyp2f1(1,1,0,1) == inf + assert hyp2f1(1,-6,-4,1) == inf + assert hyp2f1(-7.2,-0.5,-4.5,1) == 0 + assert hyp2f1(-7.2,-1,-2,1).ae(-2.6) + assert hyp2f1(1,-0.5,-4.5, 1) == inf + assert hyp2f1(1,0.5,-4.5, 1) == -inf + # Check evaluation on / close to unit circle + z = exp(j*pi/3) + w = (nthroot(2,3)+1)*exp(j*pi/12)/nthroot(3,4)**3 + assert hyp2f1('1/2','1/6','1/3', z).ae(w) + assert hyp2f1('1/2','1/6','1/3', z.conjugate()).ae(w.conjugate()) + assert hyp2f1(0.25, (1,3), 2, '0.999').ae(1.06826449496030635) + assert hyp2f1(0.25, (1,3), 2, '1.001').ae(1.06867299254830309446-0.00001446586793975874j) + assert hyp2f1(0.25, (1,3), 2, -1).ae(0.96656584492524351673) + assert hyp2f1(0.25, (1,3), 2, j).ae(0.99041766248982072266+0.03777135604180735522j) + assert hyp2f1(2,3,5,'0.99').ae(27.699347904322690602) + assert hyp2f1((3,2),-0.5,3,'0.99').ae(0.68403036843911661388) + assert hyp2f1(2,3,5,1j).ae(0.37290667145974386127+0.59210004902748285917j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), 0.95*exp(j*k)) for k in range(1,15)]).ae(52.851400204289452922+6.244285013912953225j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), 1.05*exp(j*k)) for k in range(1,15)]).ae(54.506013786220655330-3.000118813413217097j) + assert fsum([hyp2f1((7,10),(2,3),(-1,2), exp(j*k)) for k in range(1,15)]).ae(55.792077935955314887+1.731986485778500241j) + assert hyp2f1(2,2.5,-3.25,0.999).ae(218373932801217082543180041.33) + # Branches + assert hyp2f1(1,1,2,1.01).ae(4.5595744415723676911-3.1104877758314784539j) + assert hyp2f1(1,1,2,1.01+0.1j).ae(2.4149427480552782484+1.4148224796836938829j) + assert hyp2f1(1,1,2,3+4j).ae(0.14576709331407297807+0.48379185417980360773j) + assert hyp2f1(1,1,2,4).ae(-0.27465307216702742285 - 0.78539816339744830962j) + assert hyp2f1(1,1,2,-4).ae(0.40235947810852509365) + # Other: + # Cancellation with a large parameter involved (bug reported on sage-devel) + assert hyp2f1(112, (51,10), (-9,10), -0.99999).ae(-1.6241361047970862961e-24, abs_eps=0, rel_eps=eps*16) + +def test_hyper_3f2_etc(): + assert hyper([1,2,3],[1.5,8],-1).ae(0.67108992351533333030) + assert hyper([1,2,3,4],[5,6,7], -1).ae(0.90232988035425506008) + assert hyper([1,2,3],[1.25,5], 1).ae(28.924181329701905701) + assert hyper([1,2,3,4],[5,6,7],5).ae(1.5192307344006649499-1.1529845225075537461j) + assert hyper([1,2,3,4,5],[6,7,8,9],-1).ae(0.96288759462882357253) + assert hyper([1,2,3,4,5],[6,7,8,9],1).ae(1.0428697385885855841) + assert hyper([1,2,3,4,5],[6,7,8,9],5).ae(1.33980653631074769423-0.07143405251029226699j) + assert hyper([1,2.79,3.08,4.37],[5.2,6.1,7.3],5).ae(1.0996321464692607231-1.7748052293979985001j) + assert hyper([1,1,1],[1,2],1) == inf + assert hyper([1,1,1],[2,(101,100)],1).ae(100.01621213528313220) + # slow -- covered by doctests + #assert hyper([1,1,1],[2,3],0.9999).ae(1.2897972005319693905) + +def test_hyper_u(): + mp.dps = 15 + assert hyperu(2,-3,0).ae(0.05) + assert hyperu(2,-3.5,0).ae(4./99) + assert hyperu(2,0,0) == 0.5 + assert hyperu(-5,1,0) == -120 + assert hyperu(-5,2,0) == inf + assert hyperu(-5,-2,0) == 0 + assert hyperu(7,7,3).ae(0.00014681269365593503986) #exp(3)*gammainc(-6,3) + assert hyperu(2,-3,4).ae(0.011836478100271995559) + assert hyperu(3,4,5).ae(1./125) + assert hyperu(2,3,0.0625) == 256 + assert hyperu(-1,2,0.25+0.5j) == -1.75+0.5j + assert hyperu(0.5,1.5,7.25).ae(2/sqrt(29)) + assert hyperu(2,6,pi).ae(0.55804439825913399130) + assert (hyperu((3,2),8,100+201j)*10**4).ae(-0.3797318333856738798 - 2.9974928453561707782j) + assert (hyperu((5,2),(-1,2),-5000)*10**10).ae(-5.6681877926881664678j) + # XXX: fails because of undetected cancellation in low level series code + # Alternatively: could use asymptotic series here, if convergence test + # tweaked back to recognize this one + #assert (hyperu((5,2),(-1,2),-500)*10**7).ae(-1.82526906001593252847j) + +def test_hyper_2f0(): + mp.dps = 15 + assert hyper([1,2],[],3) == hyp2f0(1,2,3) + assert hyp2f0(2,3,7).ae(0.0116108068639728714668 - 0.0073727413865865802130j) + assert hyp2f0(2,3,0) == 1 + assert hyp2f0(0,0,0) == 1 + assert hyp2f0(-1,-1,1).ae(2) + assert hyp2f0(-4,1,1.5).ae(62.5) + assert hyp2f0(-4,1,50).ae(147029801) + assert hyp2f0(-4,1,0.0001).ae(0.99960011997600240000) + assert hyp2f0(0.5,0.25,0.001).ae(1.0001251174078538115) + assert hyp2f0(0.5,0.25,3+4j).ae(0.85548875824755163518 + 0.21636041283392292973j) + # Important: cancellation check + assert hyp2f0((1,6),(5,6),-0.02371708245126284498).ae(0.996785723120804309) + # Should be exact; polynomial case + assert hyp2f0(-2,1,0.5+0.5j,zeroprec=200) == 0 + assert hyp2f0(1,-2,0.5+0.5j,zeroprec=200) == 0 + # There used to be a bug in thresholds that made one of the following hang + for d in [15, 50, 80]: + mp.dps = d + assert hyp2f0(1.5, 0.5, 0.009).ae('1.006867007239309717945323585695344927904000945829843527398772456281301440034218290443367270629519483 + 1.238277162240704919639384945859073461954721356062919829456053965502443570466701567100438048602352623e-46j') + +def test_hyper_1f2(): + mp.dps = 15 + assert hyper([1],[2,3],4) == hyp1f2(1,2,3,4) + a1,b1,b2 = (1,10),(2,3),1./16 + assert hyp1f2(a1,b1,b2,10).ae(298.7482725554557568) + assert hyp1f2(a1,b1,b2,100).ae(224128961.48602947604) + assert hyp1f2(a1,b1,b2,1000).ae(1.1669528298622675109e+27) + assert hyp1f2(a1,b1,b2,10000).ae(2.4780514622487212192e+86) + assert hyp1f2(a1,b1,b2,100000).ae(1.3885391458871523997e+274) + assert hyp1f2(a1,b1,b2,1000000).ae('9.8851796978960318255e+867') + assert hyp1f2(a1,b1,b2,10**7).ae('1.1505659189516303646e+2746') + assert hyp1f2(a1,b1,b2,10**8).ae('1.4672005404314334081e+8685') + assert hyp1f2(a1,b1,b2,10**20).ae('3.6888217332150976493e+8685889636') + assert hyp1f2(a1,b1,b2,10*j).ae(-16.163252524618572878 - 44.321567896480184312j) + assert hyp1f2(a1,b1,b2,100*j).ae(61938.155294517848171 + 637349.45215942348739j) + assert hyp1f2(a1,b1,b2,1000*j).ae(8455057657257695958.7 + 6261969266997571510.6j) + assert hyp1f2(a1,b1,b2,10000*j).ae(-8.9771211184008593089e+60 + 4.6550528111731631456e+59j) + assert hyp1f2(a1,b1,b2,100000*j).ae(2.6398091437239324225e+193 + 4.1658080666870618332e+193j) + assert hyp1f2(a1,b1,b2,1000000*j).ae('3.5999042951925965458e+613 + 1.5026014707128947992e+613j') + assert hyp1f2(a1,b1,b2,10**7*j).ae('-8.3208715051623234801e+1939 - 3.6752883490851869429e+1941j') + assert hyp1f2(a1,b1,b2,10**8*j).ae('2.0724195707891484454e+6140 - 1.3276619482724266387e+6141j') + assert hyp1f2(a1,b1,b2,10**20*j).ae('-1.1734497974795488504e+6141851462 + 1.1498106965385471542e+6141851462j') + +def test_hyper_2f3(): + mp.dps = 15 + assert hyper([1,2],[3,4,5],6) == hyp2f3(1,2,3,4,5,6) + a1,a2,b1,b2,b3 = (1,10),(2,3),(3,10), 2, 1./16 + # Check asymptotic expansion + assert hyp2f3(a1,a2,b1,b2,b3,10).ae(128.98207160698659976) + assert hyp2f3(a1,a2,b1,b2,b3,1000).ae(6.6309632883131273141e25) + assert hyp2f3(a1,a2,b1,b2,b3,10000).ae(4.6863639362713340539e84) + assert hyp2f3(a1,a2,b1,b2,b3,100000).ae(8.6632451236103084119e271) + assert hyp2f3(a1,a2,b1,b2,b3,10**6).ae('2.0291718386574980641e865') + assert hyp2f3(a1,a2,b1,b2,b3,10**7).ae('7.7639836665710030977e2742') + assert hyp2f3(a1,a2,b1,b2,b3,10**8).ae('3.2537462584071268759e8681') + assert hyp2f3(a1,a2,b1,b2,b3,10**20).ae('1.2966030542911614163e+8685889627') + assert hyp2f3(a1,a2,b1,b2,b3,10*j).ae(-18.551602185587547854 - 13.348031097874113552j) + assert hyp2f3(a1,a2,b1,b2,b3,100*j).ae(78634.359124504488695 + 74459.535945281973996j) + assert hyp2f3(a1,a2,b1,b2,b3,1000*j).ae(597682550276527901.59 - 65136194809352613.078j) + assert hyp2f3(a1,a2,b1,b2,b3,10000*j).ae(-1.1779696326238582496e+59 + 1.2297607505213133872e+59j) + assert hyp2f3(a1,a2,b1,b2,b3,100000*j).ae(2.9844228969804380301e+191 + 7.5587163231490273296e+190j) + assert hyp2f3(a1,a2,b1,b2,b3,1000000*j).ae('7.4859161049322370311e+610 - 2.8467477015940090189e+610j') + assert hyp2f3(a1,a2,b1,b2,b3,10**7*j).ae('-1.7477645579418800826e+1938 - 1.7606522995808116405e+1938j') + assert hyp2f3(a1,a2,b1,b2,b3,10**8*j).ae('-1.6932731942958401784e+6137 - 2.4521909113114629368e+6137j') + assert hyp2f3(a1,a2,b1,b2,b3,10**20*j).ae('-2.0988815677627225449e+6141851451 + 5.7708223542739208681e+6141851452j') + +def test_hyper_2f2(): + mp.dps = 15 + assert hyper([1,2],[3,4],5) == hyp2f2(1,2,3,4,5) + a1,a2,b1,b2 = (3,10),4,(1,2),1./16 + assert hyp2f2(a1,a2,b1,b2,10).ae(448225936.3377556696) + assert hyp2f2(a1,a2,b1,b2,10000).ae('1.2012553712966636711e+4358') + assert hyp2f2(a1,a2,b1,b2,-20000).ae(-0.04182343755661214626) + assert hyp2f2(a1,a2,b1,b2,10**20).ae('1.1148680024303263661e+43429448190325182840') + +def test_orthpoly(): + mp.dps = 15 + assert jacobi(-4,2,3,0.7).ae(22800./4913) + assert jacobi(3,2,4,5.5) == 4133.125 + assert jacobi(1.5,5/6.,4,0).ae(-1.0851951434075508417) + assert jacobi(-2, 1, 2, 4).ae(-0.16) + assert jacobi(2, -1, 2.5, 4).ae(34.59375) + #assert jacobi(2, -1, 2, 4) == 28.5 + assert legendre(5, 7) == 129367 + assert legendre(0.5,0).ae(0.53935260118837935667) + assert legendre(-1,-1) == 1 + assert legendre(0,-1) == 1 + assert legendre(0, 1) == 1 + assert legendre(1, -1) == -1 + assert legendre(7, 1) == 1 + assert legendre(7, -1) == -1 + assert legendre(8,1.5).ae(15457523./32768) + assert legendre(j,-j).ae(2.4448182735671431011 + 0.6928881737669934843j) + assert chebyu(5,1) == 6 + assert chebyt(3,2) == 26 + assert legendre(3.5,-1) == inf + assert legendre(4.5,-1) == -inf + assert legendre(3.5+1j,-1) == mpc(inf,inf) + assert legendre(4.5+1j,-1) == mpc(-inf,-inf) + assert laguerre(4, -2, 3).ae(-1.125) + assert laguerre(3, 1+j, 0.5).ae(0.2291666666666666667 + 2.5416666666666666667j) + +def test_hermite(): + mp.dps = 15 + assert hermite(-2, 0).ae(0.5) + assert hermite(-1, 0).ae(0.88622692545275801365) + assert hermite(0, 0).ae(1) + assert hermite(1, 0) == 0 + assert hermite(2, 0).ae(-2) + assert hermite(0, 2).ae(1) + assert hermite(1, 2).ae(4) + assert hermite(1, -2).ae(-4) + assert hermite(2, -2).ae(14) + assert hermite(0.5, 0).ae(0.69136733903629335053) + assert hermite(9, 0) == 0 + assert hermite(4,4).ae(3340) + assert hermite(3,4).ae(464) + assert hermite(-4,4).ae(0.00018623860287512396181) + assert hermite(-3,4).ae(0.0016540169879668766270) + assert hermite(9, 2.5j).ae(13638725j) + assert hermite(9, -2.5j).ae(-13638725j) + assert hermite(9, 100).ae(511078883759363024000) + assert hermite(9, -100).ae(-511078883759363024000) + assert hermite(9, 100j).ae(512922083920643024000j) + assert hermite(9, -100j).ae(-512922083920643024000j) + assert hermite(-9.5, 2.5j).ae(-2.9004951258126778174e-6 + 1.7601372934039951100e-6j) + assert hermite(-9.5, -2.5j).ae(-2.9004951258126778174e-6 - 1.7601372934039951100e-6j) + assert hermite(-9.5, 100).ae(1.3776300722767084162e-22, abs_eps=0, rel_eps=eps) + assert hermite(-9.5, -100).ae('1.3106082028470671626e4355') + assert hermite(-9.5, 100j).ae(-9.7900218581864768430e-23 - 9.7900218581864768430e-23j, abs_eps=0, rel_eps=eps) + assert hermite(-9.5, -100j).ae(-9.7900218581864768430e-23 + 9.7900218581864768430e-23j, abs_eps=0, rel_eps=eps) + assert hermite(2+3j, -1-j).ae(851.3677063883687676 - 1496.4373467871007997j) + +def test_gegenbauer(): + mp.dps = 15 + assert gegenbauer(1,2,3).ae(12) + assert gegenbauer(2,3,4).ae(381) + assert gegenbauer(0,0,0) == 0 + assert gegenbauer(2,-1,3) == 0 + assert gegenbauer(-7, 0.5, 3).ae(8989) + assert gegenbauer(1, -0.5, 3).ae(-3) + assert gegenbauer(1, -1.5, 3).ae(-9) + assert gegenbauer(1, -0.5, 3).ae(-3) + assert gegenbauer(-0.5, -0.5, 3).ae(-2.6383553159023906245) + assert gegenbauer(2+3j, 1-j, 3+4j).ae(14.880536623203696780 + 20.022029711598032898j) + #assert gegenbauer(-2, -0.5, 3).ae(-12) + +def test_legenp(): + mp.dps = 15 + assert legenp(2,0,4) == legendre(2,4) + assert legenp(-2, -1, 0.5).ae(0.43301270189221932338) + assert legenp(-2, -1, 0.5, type=3).ae(0.43301270189221932338j) + assert legenp(-2, 1, 0.5).ae(-0.86602540378443864676) + assert legenp(2+j, 3+4j, -j).ae(134742.98773236786148 + 429782.72924463851745j) + assert legenp(2+j, 3+4j, -j, type=3).ae(802.59463394152268507 - 251.62481308942906447j) + assert legenp(2,4,3).ae(0) + assert legenp(2,4,3,type=3).ae(0) + assert legenp(2,1,0.5).ae(-1.2990381056766579701) + assert legenp(2,1,0.5,type=3).ae(1.2990381056766579701j) + assert legenp(3,2,3).ae(-360) + assert legenp(3,3,3).ae(240j*2**0.5) + assert legenp(3,4,3).ae(0) + assert legenp(0,0.5,2).ae(0.52503756790433198939 - 0.52503756790433198939j) + assert legenp(-1,-0.5,2).ae(0.60626116232846498110 + 0.60626116232846498110j) + assert legenp(-2,0.5,2).ae(1.5751127037129959682 - 1.5751127037129959682j) + assert legenp(-2,0.5,-0.5).ae(-0.85738275810499171286) + +def test_legenq(): + mp.dps = 15 + f = legenq + # Evaluation at poles + assert isnan(f(3,2,1)) + assert isnan(f(3,2,-1)) + assert isnan(f(3,2,1,type=3)) + assert isnan(f(3,2,-1,type=3)) + # Evaluation at 0 + assert f(0,1,0,type=2).ae(-1) + assert f(-2,2,0,type=2,zeroprec=200).ae(0) + assert f(1.5,3,0,type=2).ae(-2.2239343475841951023) + assert f(0,1,0,type=3).ae(j) + assert f(-2,2,0,type=3,zeroprec=200).ae(0) + assert f(1.5,3,0,type=3).ae(2.2239343475841951022*(1-1j)) + # Standard case, degree 0 + assert f(0,0,-1.5).ae(-0.8047189562170501873 + 1.5707963267948966192j) + assert f(0,0,-0.5).ae(-0.54930614433405484570) + assert f(0,0,0,zeroprec=200).ae(0) + assert f(0,0,0.5).ae(0.54930614433405484570) + assert f(0,0,1.5).ae(0.8047189562170501873 - 1.5707963267948966192j) + assert f(0,0,-1.5,type=3).ae(-0.80471895621705018730) + assert f(0,0,-0.5,type=3).ae(-0.5493061443340548457 - 1.5707963267948966192j) + assert f(0,0,0,type=3).ae(-1.5707963267948966192j) + assert f(0,0,0.5,type=3).ae(0.5493061443340548457 - 1.5707963267948966192j) + assert f(0,0,1.5,type=3).ae(0.80471895621705018730) + # Standard case, degree 1 + assert f(1,0,-1.5).ae(0.2070784343255752810 - 2.3561944901923449288j) + assert f(1,0,-0.5).ae(-0.72534692783297257715) + assert f(1,0,0).ae(-1) + assert f(1,0,0.5).ae(-0.72534692783297257715) + assert f(1,0,1.5).ae(0.2070784343255752810 - 2.3561944901923449288j) + # Standard case, degree 2 + assert f(2,0,-1.5).ae(-0.0635669991240192885 + 4.5160394395353277803j) + assert f(2,0,-0.5).ae(0.81866326804175685571) + assert f(2,0,0,zeroprec=200).ae(0) + assert f(2,0,0.5).ae(-0.81866326804175685571) + assert f(2,0,1.5).ae(0.0635669991240192885 - 4.5160394395353277803j) + # Misc orders and degrees + assert f(2,3,1.5,type=2).ae(-5.7243340223994616228j) + assert f(2,3,1.5,type=3).ae(-5.7243340223994616228) + assert f(2,3,0.5,type=2).ae(-12.316805742712016310) + assert f(2,3,0.5,type=3).ae(-12.316805742712016310j) + assert f(2,3,-1.5,type=2).ae(-5.7243340223994616228j) + assert f(2,3,-1.5,type=3).ae(5.7243340223994616228) + assert f(2,3,-0.5,type=2).ae(-12.316805742712016310) + assert f(2,3,-0.5,type=3).ae(-12.316805742712016310j) + assert f(2+3j, 3+4j, 0.5, type=3).ae(0.0016119404873235186807 - 0.0005885900510718119836j) + assert f(2+3j, 3+4j, -1.5, type=3).ae(0.008451400254138808670 + 0.020645193304593235298j) + assert f(-2.5,1,-1.5).ae(3.9553395527435335749j) + assert f(-2.5,1,-0.5).ae(1.9290561746445456908) + assert f(-2.5,1,0).ae(1.2708196271909686299) + assert f(-2.5,1,0.5).ae(-0.31584812990742202869) + assert f(-2.5,1,1.5).ae(-3.9553395527435335742 + 0.2993235655044701706j) + assert f(-2.5,1,-1.5,type=3).ae(0.29932356550447017254j) + assert f(-2.5,1,-0.5,type=3).ae(-0.3158481299074220287 - 1.9290561746445456908j) + assert f(-2.5,1,0,type=3).ae(1.2708196271909686292 - 1.2708196271909686299j) + assert f(-2.5,1,0.5,type=3).ae(1.9290561746445456907 + 0.3158481299074220287j) + assert f(-2.5,1,1.5,type=3).ae(-0.29932356550447017254) + +def test_agm(): + mp.dps = 15 + assert agm(0,0) == 0 + assert agm(0,1) == 0 + assert agm(1,1) == 1 + assert agm(7,7) == 7 + assert agm(j,j) == j + assert (1/agm(1,sqrt(2))).ae(0.834626841674073186) + assert agm(1,2).ae(1.4567910310469068692) + assert agm(1,3).ae(1.8636167832448965424) + assert agm(1,j).ae(0.599070117367796104+0.599070117367796104j) + assert agm(2) == agm(1,2) + assert agm(-3,4).ae(0.63468509766550907+1.3443087080896272j) + +def test_gammainc(): + mp.dps = 15 + assert gammainc(2,5).ae(6*exp(-5)) + assert gammainc(2,0,5).ae(1-6*exp(-5)) + assert gammainc(2,3,5).ae(-6*exp(-5)+4*exp(-3)) + assert gammainc(-2.5,-0.5).ae(-0.9453087204829418812-5.3164237738936178621j) + assert gammainc(0,2,4).ae(0.045121158298212213088) + assert gammainc(0,3).ae(0.013048381094197037413) + assert gammainc(0,2+j,1-j).ae(0.00910653685850304839-0.22378752918074432574j) + assert gammainc(0,1-j).ae(0.00028162445198141833+0.17932453503935894015j) + assert gammainc(3,4,5,True).ae(0.11345128607046320253) + assert gammainc(3.5,0,inf).ae(gamma(3.5)) + assert gammainc(-150.5,500).ae('6.9825435345798951153e-627') + assert gammainc(-150.5,800).ae('4.6885137549474089431e-788') + assert gammainc(-3.5, -20.5).ae(0.27008820585226911 - 1310.31447140574997636j) + assert gammainc(-3.5, -200.5).ae(0.27008820585226911 - 5.3264597096208368435e76j) # XXX real part + assert gammainc(0,0,2) == inf + assert gammainc(1,b=1).ae(0.6321205588285576784) + assert gammainc(3,2,2) == 0 + assert gammainc(2,3+j,3-j).ae(-0.28135485191849314194j) + assert gammainc(4+0j,1).ae(5.8860710587430771455) + # GH issue #301 + assert gammainc(-1,-1).ae(-0.8231640121031084799 + 3.1415926535897932385j) + assert gammainc(-2,-1).ae(1.7707229202810768576 - 1.5707963267948966192j) + assert gammainc(-3,-1).ae(-1.4963349162467073643 + 0.5235987755982988731j) + assert gammainc(-4,-1).ae(1.05365418617643814992 - 0.13089969389957471827j) + # Regularized upper gamma + assert isnan(gammainc(0, 0, regularized=True)) + assert gammainc(-1, 0, regularized=True) == inf + assert gammainc(1, 0, regularized=True) == 1 + assert gammainc(0, 5, regularized=True) == 0 + assert gammainc(0, 2+3j, regularized=True) == 0 + assert gammainc(0, 5000, regularized=True) == 0 + assert gammainc(0, 10**30, regularized=True) == 0 + assert gammainc(-1, 5, regularized=True) == 0 + assert gammainc(-1, 5000, regularized=True) == 0 + assert gammainc(-1, 10**30, regularized=True) == 0 + assert gammainc(-1, -5, regularized=True) == 0 + assert gammainc(-1, -5000, regularized=True) == 0 + assert gammainc(-1, -10**30, regularized=True) == 0 + assert gammainc(-1, 3+4j, regularized=True) == 0 + assert gammainc(1, 5, regularized=True).ae(exp(-5)) + assert gammainc(1, 5000, regularized=True).ae(exp(-5000)) + assert gammainc(1, 10**30, regularized=True).ae(exp(-10**30)) + assert gammainc(1, 3+4j, regularized=True).ae(exp(-3-4j)) + assert gammainc(-1000000,2).ae('1.3669297209397347754e-301037', abs_eps=0, rel_eps=8*eps) + assert gammainc(-1000000,2,regularized=True) == 0 + assert gammainc(-1000000,3+4j).ae('-1.322575609404222361e-698979 - 4.9274570591854533273e-698978j', abs_eps=0, rel_eps=8*eps) + assert gammainc(-1000000,3+4j,regularized=True) == 0 + assert gammainc(2+3j, 4+5j, regularized=True).ae(0.085422013530993285774-0.052595379150390078503j) + assert gammainc(1000j, 1000j, regularized=True).ae(0.49702647628921131761 + 0.00297355675013575341j) + # Generalized + assert gammainc(3,4,2) == -gammainc(3,2,4) + assert gammainc(4, 2, 3).ae(1.2593494302978947396) + assert gammainc(4, 2, 3, regularized=True).ae(0.20989157171631578993) + assert gammainc(0, 2, 3).ae(0.035852129613864082155) + assert gammainc(0, 2, 3, regularized=True) == 0 + assert gammainc(-1, 2, 3).ae(0.015219822548487616132) + assert gammainc(-1, 2, 3, regularized=True) == 0 + assert gammainc(0, 2, 3).ae(0.035852129613864082155) + assert gammainc(0, 2, 3, regularized=True) == 0 + # Should use upper gammas + assert gammainc(5, 10000, 12000).ae('1.1359381951461801687e-4327', abs_eps=0, rel_eps=8*eps) + # Should use lower gammas + assert gammainc(10000, 2, 3).ae('8.1244514125995785934e4765') + # GH issue 306 + assert gammainc(3,-1-1j) == 0 + assert gammainc(3,-1+1j) == 0 + assert gammainc(2,-1) == 0 + assert gammainc(2,-1+0j) == 0 + assert gammainc(2+0j,-1) == 0 + +def test_gammainc_expint_n(): + # These tests are intended to check all cases of the low-level code + # for upper gamma and expint with small integer index. + # Need to cover positive/negative arguments; small/large/huge arguments + # for both positive and negative indices, as well as indices 0 and 1 + # which may be special-cased + mp.dps = 15 + assert expint(-3,3.5).ae(0.021456366563296693987) + assert expint(-2,3.5).ae(0.014966633183073309405) + assert expint(-1,3.5).ae(0.011092916359219041088) + assert expint(0,3.5).ae(0.0086278238349481430685) + assert expint(1,3.5).ae(0.0069701398575483929193) + assert expint(2,3.5).ae(0.0058018939208991255223) + assert expint(3,3.5).ae(0.0049453773495857807058) + assert expint(-3,-3.5).ae(-4.6618170604073311319) + assert expint(-2,-3.5).ae(-5.5996974157555515963) + assert expint(-1,-3.5).ae(-6.7582555017739415818) + assert expint(0,-3.5).ae(-9.4615577024835182145) + assert expint(1,-3.5).ae(-13.925353995152335292 - 3.1415926535897932385j) + assert expint(2,-3.5).ae(-15.62328702434085977 - 10.995574287564276335j) + assert expint(3,-3.5).ae(-10.783026313250347722 - 19.242255003237483586j) + assert expint(-3,350).ae(2.8614825451252838069e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-2,350).ae(2.8532837224504675901e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-1,350).ae(2.8451316155828634555e-155, abs_eps=0, rel_eps=8*eps) + assert expint(0,350).ae(2.8370258275042797989e-155, abs_eps=0, rel_eps=8*eps) + assert expint(1,350).ae(2.8289659656701459404e-155, abs_eps=0, rel_eps=8*eps) + assert expint(2,350).ae(2.8209516419468505006e-155, abs_eps=0, rel_eps=8*eps) + assert expint(3,350).ae(2.8129824725501272171e-155, abs_eps=0, rel_eps=8*eps) + assert expint(-3,-350).ae(-2.8528796154044839443e+149) + assert expint(-2,-350).ae(-2.8610072121701264351e+149) + assert expint(-1,-350).ae(-2.8691813842677537647e+149) + assert expint(0,-350).ae(-2.8774025343659421709e+149) + u = expint(1,-350) + assert u.ae(-2.8856710698020863568e+149) + assert u.imag.ae(-3.1415926535897932385) + u = expint(2,-350) + assert u.ae(-2.8939874026504650534e+149) + assert u.imag.ae(-1099.5574287564276335) + u = expint(3,-350) + assert u.ae(-2.9023519497915044349e+149) + assert u.imag.ae(-192422.55003237483586) + assert expint(-3,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-2,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-1,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(0,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(1,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(2,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(3,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert expint(-3,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(-2,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(-1,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + assert expint(0,-350000000000000000000000).ae('-3.7805306852415755699e+152003068666138139677871') + u = expint(1,-350000000000000000000000) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + assert u.imag.ae(-3.1415926535897932385) + u = expint(2,-350000000000000000000000) + assert u.imag.ae(-1.0995574287564276335e+24) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + u = expint(3,-350000000000000000000000) + assert u.imag.ae(-1.9242255003237483586e+47) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + # Small case; no branch cut + assert gammainc(-3,3.5).ae(0.00010020262545203707109) + assert gammainc(-2,3.5).ae(0.00040370427343557393517) + assert gammainc(-1,3.5).ae(0.0016576839773997501492) + assert gammainc(0,3.5).ae(0.0069701398575483929193) + assert gammainc(1,3.5).ae(0.03019738342231850074) + assert gammainc(2,3.5).ae(0.13588822540043325333) + assert gammainc(3,3.5).ae(0.64169439772426814072) + # Small case; with branch cut + assert gammainc(-3,-3.5).ae(0.03595832954467563286 + 0.52359877559829887308j) + assert gammainc(-2,-3.5).ae(-0.88024704597962022221 - 1.5707963267948966192j) + assert gammainc(-1,-3.5).ae(4.4637962926688170771 + 3.1415926535897932385j) + assert gammainc(0,-3.5).ae(-13.925353995152335292 - 3.1415926535897932385j) + assert gammainc(1,-3.5).ae(33.115451958692313751) + assert gammainc(2,-3.5).ae(-82.788629896730784377) + assert gammainc(3,-3.5).ae(240.08702670051927469) + # Asymptotic case; no branch cut + assert gammainc(-3,350).ae(6.5424095113340358813e-163, abs_eps=0, rel_eps=8*eps) + assert gammainc(-2,350).ae(2.296312222489899769e-160, abs_eps=0, rel_eps=8*eps) + assert gammainc(-1,350).ae(8.059861834133858573e-158, abs_eps=0, rel_eps=8*eps) + assert gammainc(0,350).ae(2.8289659656701459404e-155, abs_eps=0, rel_eps=8*eps) + assert gammainc(1,350).ae(9.9295903962649792963e-153, abs_eps=0, rel_eps=8*eps) + assert gammainc(2,350).ae(3.485286229089007733e-150, abs_eps=0, rel_eps=8*eps) + assert gammainc(3,350).ae(1.2233453960006379793e-147, abs_eps=0, rel_eps=8*eps) + # Asymptotic case; branch cut + u = gammainc(-3,-350) + assert u.ae(6.7889565783842895085e+141) + assert u.imag.ae(0.52359877559829887308) + u = gammainc(-2,-350) + assert u.ae(-2.3692668977889832121e+144) + assert u.imag.ae(-1.5707963267948966192) + u = gammainc(-1,-350) + assert u.ae(8.2685354361441858669e+146) + assert u.imag.ae(3.1415926535897932385) + u = gammainc(0,-350) + assert u.ae(-2.8856710698020863568e+149) + assert u.imag.ae(-3.1415926535897932385) + u = gammainc(1,-350) + assert u.ae(1.0070908870280797598e+152) + assert u.imag == 0 + u = gammainc(2,-350) + assert u.ae(-3.5147471957279983618e+154) + assert u.imag == 0 + u = gammainc(3,-350) + assert u.ae(1.2266568422179417091e+157) + assert u.imag == 0 + # Extreme asymptotic case + assert gammainc(-3,350000000000000000000000).ae('5.0362468738874738859e-152003068666138139677990', abs_eps=0, rel_eps=8*eps) + assert gammainc(-2,350000000000000000000000).ae('1.7626864058606158601e-152003068666138139677966', abs_eps=0, rel_eps=8*eps) + assert gammainc(-1,350000000000000000000000).ae('6.1694024205121555102e-152003068666138139677943', abs_eps=0, rel_eps=8*eps) + assert gammainc(0,350000000000000000000000).ae('2.1592908471792544286e-152003068666138139677919', abs_eps=0, rel_eps=8*eps) + assert gammainc(1,350000000000000000000000).ae('7.5575179651273905e-152003068666138139677896', abs_eps=0, rel_eps=8*eps) + assert gammainc(2,350000000000000000000000).ae('2.645131287794586675e-152003068666138139677872', abs_eps=0, rel_eps=8*eps) + assert gammainc(3,350000000000000000000000).ae('9.2579595072810533625e-152003068666138139677849', abs_eps=0, rel_eps=8*eps) + u = gammainc(-3,-350000000000000000000000) + assert u.ae('8.8175642804468234866e+152003068666138139677800') + assert u.imag.ae(0.52359877559829887308) + u = gammainc(-2,-350000000000000000000000) + assert u.ae('-3.0861474981563882203e+152003068666138139677824') + assert u.imag.ae(-1.5707963267948966192) + u = gammainc(-1,-350000000000000000000000) + assert u.ae('1.0801516243547358771e+152003068666138139677848') + assert u.imag.ae(3.1415926535897932385) + u = gammainc(0,-350000000000000000000000) + assert u.ae('-3.7805306852415755699e+152003068666138139677871') + assert u.imag.ae(-3.1415926535897932385) + assert gammainc(1,-350000000000000000000000).ae('1.3231857398345514495e+152003068666138139677895') + assert gammainc(2,-350000000000000000000000).ae('-4.6311500894209300731e+152003068666138139677918') + assert gammainc(3,-350000000000000000000000).ae('1.6209025312973255256e+152003068666138139677942') + +def test_incomplete_beta(): + mp.dps = 15 + assert betainc(-2,-3,0.5,0.75).ae(63.4305673311255413583969) + assert betainc(4.5,0.5+2j,2.5,6).ae(0.2628801146130621387903065 + 0.5162565234467020592855378j) + assert betainc(4,5,0,6).ae(90747.77142857142857142857) + +def test_erf(): + mp.dps = 15 + assert erf(0) == 0 + assert erf(1).ae(0.84270079294971486934) + assert erf(3+4j).ae(-120.186991395079444098 - 27.750337293623902498j) + assert erf(-4-3j).ae(-0.99991066178539168236 + 0.00004972026054496604j) + assert erf(pi).ae(0.99999112385363235839) + assert erf(1j).ae(1.6504257587975428760j) + assert erf(-1j).ae(-1.6504257587975428760j) + assert isinstance(erf(1), mpf) + assert isinstance(erf(-1), mpf) + assert isinstance(erf(0), mpf) + assert isinstance(erf(0j), mpc) + assert erf(inf) == 1 + assert erf(-inf) == -1 + assert erfi(0) == 0 + assert erfi(1/pi).ae(0.371682698493894314) + assert erfi(inf) == inf + assert erfi(-inf) == -inf + assert erf(1+0j) == erf(1) + assert erfc(1+0j) == erfc(1) + assert erf(0.2+0.5j).ae(1 - erfc(0.2+0.5j)) + assert erfc(0) == 1 + assert erfc(1).ae(1-erf(1)) + assert erfc(-1).ae(1-erf(-1)) + assert erfc(1/pi).ae(1-erf(1/pi)) + assert erfc(-10) == 2 + assert erfc(-1000000) == 2 + assert erfc(-inf) == 2 + assert erfc(inf) == 0 + assert isnan(erfc(nan)) + assert (erfc(10**4)*mpf(10)**43429453).ae('3.63998738656420') + assert erf(8+9j).ae(-1072004.2525062051158 + 364149.91954310255423j) + assert erfc(8+9j).ae(1072005.2525062051158 - 364149.91954310255423j) + assert erfc(-8-9j).ae(-1072003.2525062051158 + 364149.91954310255423j) + mp.dps = 50 + # This one does not use the asymptotic series + assert (erfc(10)*10**45).ae('2.0884875837625447570007862949577886115608181193212') + # This one does + assert (erfc(50)*10**1088).ae('2.0709207788416560484484478751657887929322509209954') + mp.dps = 15 + assert str(erfc(10**50)) == '3.66744826532555e-4342944819032518276511289189166050822943970058036665661144537831658646492088707747292249493384317534' + assert erfinv(0) == 0 + assert erfinv(0.5).ae(0.47693627620446987338) + assert erfinv(-0.5).ae(-0.47693627620446987338) + assert erfinv(1) == inf + assert erfinv(-1) == -inf + assert erf(erfinv(0.95)).ae(0.95) + assert erf(erfinv(0.999999999995)).ae(0.999999999995) + assert erf(erfinv(-0.999999999995)).ae(-0.999999999995) + mp.dps = 50 + assert erf(erfinv('0.99999999999999999999999999999995')).ae('0.99999999999999999999999999999995') + assert erf(erfinv('0.999999999999999999999999999999995')).ae('0.999999999999999999999999999999995') + assert erf(erfinv('-0.999999999999999999999999999999995')).ae('-0.999999999999999999999999999999995') + mp.dps = 15 + # Complex asymptotic expansions + v = erfc(50j) + assert v.real == 1 + assert v.imag.ae('-6.1481820666053078736e+1083') + assert erfc(-100+5j).ae(2) + assert (erfc(100+5j)*10**4335).ae(2.3973567853824133572 - 3.9339259530609420597j) + assert erfc(100+100j).ae(0.00065234366376857698698 - 0.0039357263629214118437j) + +def test_pdf(): + mp.dps = 15 + assert npdf(-inf) == 0 + assert npdf(inf) == 0 + assert npdf(5,0,2).ae(npdf(5+4,4,2)) + assert quadts(lambda x: npdf(x,-0.5,0.8), [-inf, inf]) == 1 + assert ncdf(0) == 0.5 + assert ncdf(3,3) == 0.5 + assert ncdf(-inf) == 0 + assert ncdf(inf) == 1 + assert ncdf(10) == 1 + # Verify that this is computed accurately + assert (ncdf(-10)*10**24).ae(7.619853024160526) + +def test_lambertw(): + mp.dps = 15 + assert lambertw(0) == 0 + assert lambertw(0+0j) == 0 + assert lambertw(inf) == inf + assert isnan(lambertw(nan)) + assert lambertw(inf,1).real == inf + assert lambertw(inf,1).imag.ae(2*pi) + assert lambertw(-inf,1).real == inf + assert lambertw(-inf,1).imag.ae(3*pi) + assert lambertw(0,-1) == -inf + assert lambertw(0,1) == -inf + assert lambertw(0,3) == -inf + assert lambertw(e).ae(1) + assert lambertw(1).ae(0.567143290409783873) + assert lambertw(-pi/2).ae(j*pi/2) + assert lambertw(-log(2)/2).ae(-log(2)) + assert lambertw(0.25).ae(0.203888354702240164) + assert lambertw(-0.25).ae(-0.357402956181388903) + assert lambertw(-1./10000,0).ae(-0.000100010001500266719) + assert lambertw(-0.25,-1).ae(-2.15329236411034965) + assert lambertw(0.25,-1).ae(-3.00899800997004620-4.07652978899159763j) + assert lambertw(-0.25,-1).ae(-2.15329236411034965) + assert lambertw(0.25,1).ae(-3.00899800997004620+4.07652978899159763j) + assert lambertw(-0.25,1).ae(-3.48973228422959210+7.41405453009603664j) + assert lambertw(-4).ae(0.67881197132094523+1.91195078174339937j) + assert lambertw(-4,1).ae(-0.66743107129800988+7.76827456802783084j) + assert lambertw(-4,-1).ae(0.67881197132094523-1.91195078174339937j) + assert lambertw(1000).ae(5.24960285240159623) + assert lambertw(1000,1).ae(4.91492239981054535+5.44652615979447070j) + assert lambertw(1000,-1).ae(4.91492239981054535-5.44652615979447070j) + assert lambertw(1000,5).ae(3.5010625305312892+29.9614548941181328j) + assert lambertw(3+4j).ae(1.281561806123775878+0.533095222020971071j) + assert lambertw(-0.4+0.4j).ae(-0.10396515323290657+0.61899273315171632j) + assert lambertw(3+4j,1).ae(-0.11691092896595324+5.61888039871282334j) + assert lambertw(3+4j,-1).ae(0.25856740686699742-3.85211668616143559j) + assert lambertw(-0.5,-1).ae(-0.794023632344689368-0.770111750510379110j) + assert lambertw(-1./10000,1).ae(-11.82350837248724344+6.80546081842002101j) + assert lambertw(-1./10000,-1).ae(-11.6671145325663544) + assert lambertw(-1./10000,-2).ae(-11.82350837248724344-6.80546081842002101j) + assert lambertw(-1./100000,4).ae(-14.9186890769540539+26.1856750178782046j) + assert lambertw(-1./100000,5).ae(-15.0931437726379218666+32.5525721210262290086j) + assert lambertw((2+j)/10).ae(0.173704503762911669+0.071781336752835511j) + assert lambertw((2+j)/10,1).ae(-3.21746028349820063+4.56175438896292539j) + assert lambertw((2+j)/10,-1).ae(-3.03781405002993088-3.53946629633505737j) + assert lambertw((2+j)/10,4).ae(-4.6878509692773249+23.8313630697683291j) + assert lambertw(-(2+j)/10).ae(-0.226933772515757933-0.164986470020154580j) + assert lambertw(-(2+j)/10,1).ae(-2.43569517046110001+0.76974067544756289j) + assert lambertw(-(2+j)/10,-1).ae(-3.54858738151989450-6.91627921869943589j) + assert lambertw(-(2+j)/10,4).ae(-4.5500846928118151+20.6672982215434637j) + mp.dps = 50 + assert lambertw(pi).ae('1.073658194796149172092178407024821347547745350410314531') + mp.dps = 15 + # Former bug in generated branch + assert lambertw(-0.5+0.002j).ae(-0.78917138132659918344 + 0.76743539379990327749j) + assert lambertw(-0.5-0.002j).ae(-0.78917138132659918344 - 0.76743539379990327749j) + assert lambertw(-0.448+0.4j).ae(-0.11855133765652382241 + 0.66570534313583423116j) + assert lambertw(-0.448-0.4j).ae(-0.11855133765652382241 - 0.66570534313583423116j) + assert lambertw(-0.65475+0.0001j).ae(-0.61053421111385310898+1.0396534993944097723803j) + # Huge branch index + w = lambertw(1,10**20) + assert w.real.ae(-47.889578926290259164) + assert w.imag.ae(6.2831853071795864769e+20) + +def test_lambertw_hard(): + def check(x,y): + y = convert(y) + type_ok = True + if isinstance(y, mpf): + type_ok = isinstance(x, mpf) + real_ok = abs(x.real-y.real) <= abs(y.real)*8*eps + imag_ok = abs(x.imag-y.imag) <= abs(y.imag)*8*eps + #print x, y, abs(x.real-y.real), abs(x.imag-y.imag) + return real_ok and imag_ok + # Evaluation near 0 + mp.dps = 15 + assert check(lambertw(1e-10), 9.999999999000000000e-11) + assert check(lambertw(-1e-10), -1.000000000100000000e-10) + assert check(lambertw(1e-10j), 9.999999999999999999733e-21 + 9.99999999999999999985e-11j) + assert check(lambertw(-1e-10j), 9.999999999999999999733e-21 - 9.99999999999999999985e-11j) + assert check(lambertw(1e-10,1), -26.303186778379041559 + 3.265093911703828397j) + assert check(lambertw(-1e-10,1), -26.326236166739163892 + 6.526183280686333315j) + assert check(lambertw(1e-10j,1), -26.312931726911421551 + 4.896366881798013421j) + assert check(lambertw(-1e-10j,1), -26.297238779529035066 + 1.632807161345576513j) + assert check(lambertw(1e-10,-1), -26.303186778379041559 - 3.265093911703828397j) + assert check(lambertw(-1e-10,-1), -26.295238819246925694) + assert check(lambertw(1e-10j,-1), -26.297238779529035028 - 1.6328071613455765135j) + assert check(lambertw(-1e-10j,-1), -26.312931726911421551 - 4.896366881798013421j) + # Test evaluation very close to the branch point -1/e + # on the -1, 0, and 1 branches + add = lambda x, y: fadd(x,y,exact=True) + sub = lambda x, y: fsub(x,y,exact=True) + addj = lambda x, y: fadd(x,fmul(y,1j,exact=True),exact=True) + subj = lambda x, y: fadd(x,fmul(y,-1j,exact=True),exact=True) + mp.dps = 1500 + a = -1/e + 10*eps + d3 = mpf('1e-3') + d10 = mpf('1e-10') + d20 = mpf('1e-20') + d40 = mpf('1e-40') + d80 = mpf('1e-80') + d300 = mpf('1e-300') + d1000 = mpf('1e-1000') + mp.dps = 15 + # ---- Branch 0 ---- + # -1/e + eps + assert check(lambertw(add(a,d3)), -0.92802015005456704876) + assert check(lambertw(add(a,d10)), -0.99997668374140088071) + assert check(lambertw(add(a,d20)), -0.99999999976683560186) + assert lambertw(add(a,d40)) == -1 + assert lambertw(add(a,d80)) == -1 + assert lambertw(add(a,d300)) == -1 + assert lambertw(add(a,d1000)) == -1 + # -1/e - eps + assert check(lambertw(sub(a,d3)), -0.99819016149860989001+0.07367191188934638577j) + assert check(lambertw(sub(a,d10)), -0.9999999998187812114595992+0.0000233164398140346109194j) + assert check(lambertw(sub(a,d20)), -0.99999999999999999998187+2.331643981597124203344e-10j) + assert check(lambertw(sub(a,d40)), -1.0+2.33164398159712420336e-20j) + assert check(lambertw(sub(a,d80)), -1.0+2.33164398159712420336e-40j) + assert check(lambertw(sub(a,d300)), -1.0+2.33164398159712420336e-150j) + assert check(lambertw(sub(a,d1000)), mpc(-1,'2.33164398159712420336e-500')) + # -1/e + eps*j + assert check(lambertw(addj(a,d3)), -0.94790387486938526634+0.05036819639190132490j) + assert check(lambertw(addj(a,d10)), -0.9999835127872943680999899+0.0000164870314895821225256j) + assert check(lambertw(addj(a,d20)), -0.999999999835127872929987+1.64872127051890935830e-10j) + assert check(lambertw(addj(a,d40)), -0.9999999999999999999835+1.6487212707001281468305e-20j) + assert check(lambertw(addj(a,d80)), -1.0 + 1.64872127070012814684865e-40j) + assert check(lambertw(addj(a,d300)), -1.0 + 1.64872127070012814684865e-150j) + assert check(lambertw(addj(a,d1000)), mpc(-1.0,'1.64872127070012814684865e-500')) + # -1/e - eps*j + assert check(lambertw(subj(a,d3)), -0.94790387486938526634-0.05036819639190132490j) + assert check(lambertw(subj(a,d10)), -0.9999835127872943680999899-0.0000164870314895821225256j) + assert check(lambertw(subj(a,d20)), -0.999999999835127872929987-1.64872127051890935830e-10j) + assert check(lambertw(subj(a,d40)), -0.9999999999999999999835-1.6487212707001281468305e-20j) + assert check(lambertw(subj(a,d80)), -1.0 - 1.64872127070012814684865e-40j) + assert check(lambertw(subj(a,d300)), -1.0 - 1.64872127070012814684865e-150j) + assert check(lambertw(subj(a,d1000)), mpc(-1.0,'-1.64872127070012814684865e-500')) + # ---- Branch 1 ---- + assert check(lambertw(addj(a,d3),1), -3.088501303219933378005990 + 7.458676867597474813950098j) + assert check(lambertw(addj(a,d80),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(addj(a,d300),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(addj(a,d1000),1), -3.088843015613043855957087 + 7.461489285654254556906117j) + assert check(lambertw(subj(a,d3),1), -1.0520914180450129534365906 + 0.0539925638125450525673175j) + assert check(lambertw(subj(a,d10),1), -1.0000164872127056318529390 + 0.000016487393927159250398333077j) + assert check(lambertw(subj(a,d20),1), -1.0000000001648721270700128 + 1.64872127088134693542628e-10j) + assert check(lambertw(subj(a,d40),1), -1.000000000000000000016487 + 1.64872127070012814686677e-20j) + assert check(lambertw(subj(a,d80),1), -1.0 + 1.64872127070012814684865e-40j) + assert check(lambertw(subj(a,d300),1), -1.0 + 1.64872127070012814684865e-150j) + assert check(lambertw(subj(a,d1000),1), mpc(-1.0, '1.64872127070012814684865e-500')) + # ---- Branch -1 ---- + # -1/e + eps + assert check(lambertw(add(a,d3),-1), -1.075608941186624989414945) + assert check(lambertw(add(a,d10),-1), -1.000023316621036696460620) + assert check(lambertw(add(a,d20),-1), -1.000000000233164398177834) + assert lambertw(add(a,d40),-1) == -1 + assert lambertw(add(a,d80),-1) == -1 + assert lambertw(add(a,d300),-1) == -1 + assert lambertw(add(a,d1000),-1) == -1 + # -1/e - eps + assert check(lambertw(sub(a,d3),-1), -0.99819016149860989001-0.07367191188934638577j) + assert check(lambertw(sub(a,d10),-1), -0.9999999998187812114595992-0.0000233164398140346109194j) + assert check(lambertw(sub(a,d20),-1), -0.99999999999999999998187-2.331643981597124203344e-10j) + assert check(lambertw(sub(a,d40),-1), -1.0-2.33164398159712420336e-20j) + assert check(lambertw(sub(a,d80),-1), -1.0-2.33164398159712420336e-40j) + assert check(lambertw(sub(a,d300),-1), -1.0-2.33164398159712420336e-150j) + assert check(lambertw(sub(a,d1000),-1), mpc(-1,'-2.33164398159712420336e-500')) + # -1/e + eps*j + assert check(lambertw(addj(a,d3),-1), -1.0520914180450129534365906 - 0.0539925638125450525673175j) + assert check(lambertw(addj(a,d10),-1), -1.0000164872127056318529390 - 0.0000164873939271592503983j) + assert check(lambertw(addj(a,d20),-1), -1.0000000001648721270700 - 1.64872127088134693542628e-10j) + assert check(lambertw(addj(a,d40),-1), -1.00000000000000000001648 - 1.6487212707001281468667726e-20j) + assert check(lambertw(addj(a,d80),-1), -1.0 - 1.64872127070012814684865e-40j) + assert check(lambertw(addj(a,d300),-1), -1.0 - 1.64872127070012814684865e-150j) + assert check(lambertw(addj(a,d1000),-1), mpc(-1.0,'-1.64872127070012814684865e-500')) + # -1/e - eps*j + assert check(lambertw(subj(a,d3),-1), -3.088501303219933378005990-7.458676867597474813950098j) + assert check(lambertw(subj(a,d10),-1), -3.088843015579260686911033-7.461489285372968780020716j) + assert check(lambertw(subj(a,d20),-1), -3.088843015613043855953708-7.461489285654254556877988j) + assert check(lambertw(subj(a,d40),-1), -3.088843015613043855957087-7.461489285654254556906117j) + assert check(lambertw(subj(a,d80),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + assert check(lambertw(subj(a,d300),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + assert check(lambertw(subj(a,d1000),-1), -3.088843015613043855957087 - 7.461489285654254556906117j) + # One more case, testing higher precision + mp.dps = 500 + x = -1/e + mpf('1e-13') + ans = "-0.99999926266961377166355784455394913638782494543377383"\ + "744978844374498153493943725364881490261187530235150668593869563"\ + "168276697689459394902153960200361935311512317183678882" + mp.dps = 15 + assert lambertw(x).ae(ans) + mp.dps = 50 + assert lambertw(x).ae(ans) + mp.dps = 150 + assert lambertw(x).ae(ans) + +def test_meijerg(): + mp.dps = 15 + assert meijerg([[2,3],[1]],[[0.5,2],[3,4]], 2.5).ae(4.2181028074787439386) + assert meijerg([[],[1+j]],[[1],[1]], 3+4j).ae(271.46290321152464592 - 703.03330399954820169j) + assert meijerg([[0.25],[1]],[[0.5],[2]],0) == 0 + assert meijerg([[0],[]],[[0,0,'1/3','2/3'], []], '2/27').ae(2.2019391389653314120) + # Verify 1/z series being used + assert meijerg([[-3],[-0.5]], [[-1],[-2.5]], -0.5).ae(-1.338096165935754898687431) + assert meijerg([[1-(-1)],[1-(-2.5)]], [[1-(-3)],[1-(-0.5)]], -2.0).ae(-1.338096165935754898687431) + assert meijerg([[-3],[-0.5]], [[-1],[-2.5]], -1).ae(-(pi+4)/(4*pi)) + a = 2.5 + b = 1.25 + for z in [mpf(0.25), mpf(2)]: + x1 = hyp1f1(a,b,z) + x2 = gamma(b)/gamma(a)*meijerg([[1-a],[]],[[0],[1-b]],-z) + x3 = gamma(b)/gamma(a)*meijerg([[1-0],[1-(1-b)]],[[1-(1-a)],[]],-1/z) + assert x1.ae(x2) + assert x1.ae(x3) + +def test_appellf1(): + mp.dps = 15 + assert appellf1(2,-2,1,1,2,3).ae(-1.75) + assert appellf1(2,1,-2,1,2,3).ae(-8) + assert appellf1(2,1,-2,1,0.5,0.25).ae(1.5) + assert appellf1(-2,1,3,2,3,3).ae(19) + assert appellf1(1,2,3,4,0.5,0.125).ae( 1.53843285792549786518) + +def test_coulomb(): + # Note: most tests are doctests + # Test for a bug: + mp.dps = 15 + assert coulombg(mpc(-5,0),2,3).ae(20.087729487721430394) + +def test_hyper_param_accuracy(): + mp.dps = 15 + As = [n+1e-10 for n in range(-5,-1)] + Bs = [n+1e-10 for n in range(-12,-5)] + assert hyper(As,Bs,10).ae(-381757055858.652671927) + assert legenp(0.5, 100, 0.25).ae(-2.4124576567211311755e+144) + assert (hyp1f1(1000,1,-100)*10**24).ae(5.2589445437370169113) + assert (hyp2f1(10, -900, 10.5, 0.99)*10**24).ae(1.9185370579660768203) + assert (hyp2f1(1000,1.5,-3.5,-1.5)*10**385).ae(-2.7367529051334000764) + assert hyp2f1(-5, 10, 3, 0.5, zeroprec=500) == 0 + assert (hyp1f1(-10000, 1000, 100)*10**424).ae(-3.1046080515824859974) + assert (hyp2f1(1000,1.5,-3.5,-0.75,maxterms=100000)*10**231).ae(-4.0534790813913998643) + assert legenp(2, 3, 0.25) == 0 + pytest.raises(ValueError, lambda: hypercomb(lambda a: [([],[],[],[],[a],[-a],0.5)], [3])) + assert hypercomb(lambda a: [([],[],[],[],[a],[-a],0.5)], [3], infprec=200) == inf + assert meijerg([[],[]],[[0,0,0,0],[]],0.1).ae(1.5680822343832351418) + assert (besselk(400,400)*10**94).ae(1.4387057277018550583) + mp.dps = 5 + (hyp1f1(-5000.5, 1500, 100)*10**185).ae(8.5185229673381935522) + (hyp1f1(-5000, 1500, 100)*10**185).ae(9.1501213424563944311) + mp.dps = 15 + (hyp1f1(-5000.5, 1500, 100)*10**185).ae(8.5185229673381935522) + (hyp1f1(-5000, 1500, 100)*10**185).ae(9.1501213424563944311) + assert hyp0f1(fadd(-20,'1e-100',exact=True), 0.25).ae(1.85014429040102783e+49) + assert hyp0f1((-20*10**100+1, 10**100), 0.25).ae(1.85014429040102783e+49) + +def test_hypercomb_zero_pow(): + # check that 0^0 = 1 + assert hypercomb(lambda a: (([0],[a],[],[],[],[],0),), [0]) == 1 + assert meijerg([[-1.5],[]],[[0],[-0.75]],0).ae(1.4464090846320771425) + +def test_spherharm(): + mp.dps = 15 + t = 0.5; r = 0.25 + assert spherharm(0,0,t,r).ae(0.28209479177387814347) + assert spherharm(1,-1,t,r).ae(0.16048941205971996369 - 0.04097967481096344271j) + assert spherharm(1,0,t,r).ae(0.42878904414183579379) + assert spherharm(1,1,t,r).ae(-0.16048941205971996369 - 0.04097967481096344271j) + assert spherharm(2,-2,t,r).ae(0.077915886919031181734 - 0.042565643022253962264j) + assert spherharm(2,-1,t,r).ae(0.31493387233497459884 - 0.08041582001959297689j) + assert spherharm(2,0,t,r).ae(0.41330596756220761898) + assert spherharm(2,1,t,r).ae(-0.31493387233497459884 - 0.08041582001959297689j) + assert spherharm(2,2,t,r).ae(0.077915886919031181734 + 0.042565643022253962264j) + assert spherharm(3,-3,t,r).ae(0.033640236589690881646 - 0.031339125318637082197j) + assert spherharm(3,-2,t,r).ae(0.18091018743101461963 - 0.09883168583167010241j) + assert spherharm(3,-1,t,r).ae(0.42796713930907320351 - 0.10927795157064962317j) + assert spherharm(3,0,t,r).ae(0.27861659336351639787) + assert spherharm(3,1,t,r).ae(-0.42796713930907320351 - 0.10927795157064962317j) + assert spherharm(3,2,t,r).ae(0.18091018743101461963 + 0.09883168583167010241j) + assert spherharm(3,3,t,r).ae(-0.033640236589690881646 - 0.031339125318637082197j) + assert spherharm(0,-1,t,r) == 0 + assert spherharm(0,-2,t,r) == 0 + assert spherharm(0,1,t,r) == 0 + assert spherharm(0,2,t,r) == 0 + assert spherharm(1,2,t,r) == 0 + assert spherharm(1,3,t,r) == 0 + assert spherharm(1,-2,t,r) == 0 + assert spherharm(1,-3,t,r) == 0 + assert spherharm(2,3,t,r) == 0 + assert spherharm(2,4,t,r) == 0 + assert spherharm(2,-3,t,r) == 0 + assert spherharm(2,-4,t,r) == 0 + assert spherharm(3,4.5,0.5,0.25).ae(-22.831053442240790148 + 10.910526059510013757j) + assert spherharm(2+3j, 1-j, 1+j, 3+4j).ae(-2.6582752037810116935 - 1.0909214905642160211j) + assert spherharm(-6,2.5,t,r).ae(0.39383644983851448178 + 0.28414687085358299021j) + assert spherharm(-3.5, 3, 0.5, 0.25).ae(0.014516852987544698924 - 0.015582769591477628495j) + assert spherharm(-3, 3, 0.5, 0.25) == 0 + assert spherharm(-6, 3, 0.5, 0.25).ae(-0.16544349818782275459 - 0.15412657723253924562j) + assert spherharm(-6, 1.5, 0.5, 0.25).ae(0.032208193499767402477 + 0.012678000924063664921j) + assert spherharm(3,0,0,1).ae(0.74635266518023078283) + assert spherharm(3,-2,0,1) == 0 + assert spherharm(3,-2,1,1).ae(-0.16270707338254028971 - 0.35552144137546777097j) + +def test_qfunctions(): + mp.dps = 15 + assert qp(2,3,100).ae('2.7291482267247332183e2391') + +def test_issue_239(): + mp.prec = 150 + x = ldexp(2476979795053773,-52) + assert betainc(206, 385, 0, 0.55, 1).ae('0.99999999999999999999996570910644857895771110649954') + mp.dps = 15 + pytest.raises(ValueError, lambda: hyp2f1(-5,5,0.5,0.5)) + +# Extra stress testing for Bessel functions +# Reference zeros generated with the aid of scipy.special +# jn_zero, jnp_zero, yn_zero, ynp_zero + +V = 15 +M = 15 + +jn_small_zeros = \ +[[2.4048255576957728, + 5.5200781102863106, + 8.6537279129110122, + 11.791534439014282, + 14.930917708487786, + 18.071063967910923, + 21.211636629879259, + 24.352471530749303, + 27.493479132040255, + 30.634606468431975, + 33.775820213573569, + 36.917098353664044, + 40.058425764628239, + 43.19979171317673, + 46.341188371661814], + [3.8317059702075123, + 7.0155866698156188, + 10.173468135062722, + 13.323691936314223, + 16.470630050877633, + 19.615858510468242, + 22.760084380592772, + 25.903672087618383, + 29.046828534916855, + 32.189679910974404, + 35.332307550083865, + 38.474766234771615, + 41.617094212814451, + 44.759318997652822, + 47.901460887185447], + [5.1356223018406826, + 8.4172441403998649, + 11.619841172149059, + 14.795951782351261, + 17.959819494987826, + 21.116997053021846, + 24.270112313573103, + 27.420573549984557, + 30.569204495516397, + 33.7165195092227, + 36.86285651128381, + 40.008446733478192, + 43.153453778371463, + 46.297996677236919, + 49.442164110416873], + [6.3801618959239835, + 9.7610231299816697, + 13.015200721698434, + 16.223466160318768, + 19.409415226435012, + 22.582729593104442, + 25.748166699294978, + 28.908350780921758, + 32.064852407097709, + 35.218670738610115, + 38.370472434756944, + 41.520719670406776, + 44.669743116617253, + 47.817785691533302, + 50.965029906205183], + [7.5883424345038044, + 11.064709488501185, + 14.37253667161759, + 17.615966049804833, + 20.826932956962388, + 24.01901952477111, + 27.199087765981251, + 30.371007667117247, + 33.537137711819223, + 36.699001128744649, + 39.857627302180889, + 43.01373772335443, + 46.167853512924375, + 49.320360686390272, + 52.471551398458023], + [8.771483815959954, + 12.338604197466944, + 15.700174079711671, + 18.980133875179921, + 22.217799896561268, + 25.430341154222704, + 28.626618307291138, + 31.811716724047763, + 34.988781294559295, + 38.159868561967132, + 41.326383254047406, + 44.489319123219673, + 47.649399806697054, + 50.80716520300633, + 53.963026558378149], + [9.9361095242176849, + 13.589290170541217, + 17.003819667816014, + 20.320789213566506, + 23.58608443558139, + 26.820151983411405, + 30.033722386570469, + 33.233041762847123, + 36.422019668258457, + 39.603239416075404, + 42.778481613199507, + 45.949015998042603, + 49.11577372476426, + 52.279453903601052, + 55.440592068853149], + [11.086370019245084, + 14.821268727013171, + 18.287582832481726, + 21.641541019848401, + 24.934927887673022, + 28.191188459483199, + 31.42279419226558, + 34.637089352069324, + 37.838717382853611, + 41.030773691585537, + 44.21540850526126, + 47.394165755570512, + 50.568184679795566, + 53.738325371963291, + 56.905249991978781], + [12.225092264004655, + 16.037774190887709, + 19.554536430997055, + 22.94517313187462, + 26.266814641176644, + 29.54565967099855, + 32.795800037341462, + 36.025615063869571, + 39.240447995178135, + 42.443887743273558, + 45.638444182199141, + 48.825930381553857, + 52.007691456686903, + 55.184747939289049, + 58.357889025269694], + [13.354300477435331, + 17.241220382489128, + 20.807047789264107, + 24.233885257750552, + 27.583748963573006, + 30.885378967696675, + 34.154377923855096, + 37.400099977156589, + 40.628553718964528, + 43.843801420337347, + 47.048700737654032, + 50.245326955305383, + 53.435227157042058, + 56.619580266508436, + 59.799301630960228], + [14.475500686554541, + 18.433463666966583, + 22.046985364697802, + 25.509450554182826, + 28.887375063530457, + 32.211856199712731, + 35.499909205373851, + 38.761807017881651, + 42.004190236671805, + 45.231574103535045, + 48.447151387269394, + 51.653251668165858, + 54.851619075963349, + 58.043587928232478, + 61.230197977292681], + [15.589847884455485, + 19.61596690396692, + 23.275853726263409, + 26.773322545509539, + 30.17906117878486, + 33.526364075588624, + 36.833571341894905, + 40.111823270954241, + 43.368360947521711, + 46.608132676274944, + 49.834653510396724, + 53.050498959135054, + 56.257604715114484, + 59.457456908388002, + 62.651217388202912], + [16.698249933848246, + 20.789906360078443, + 24.494885043881354, + 28.026709949973129, + 31.45996003531804, + 34.829986990290238, + 38.156377504681354, + 41.451092307939681, + 44.721943543191147, + 47.974293531269048, + 51.211967004101068, + 54.437776928325074, + 57.653844811906946, + 60.8618046824805, + 64.062937824850136], + [17.801435153282442, + 21.95624406783631, + 25.705103053924724, + 29.270630441874802, + 32.731053310978403, + 36.123657666448762, + 39.469206825243883, + 42.780439265447158, + 46.06571091157561, + 49.330780096443524, + 52.579769064383396, + 55.815719876305778, + 59.040934037249271, + 62.257189393731728, + 65.465883797232125], + [18.899997953174024, + 23.115778347252756, + 26.907368976182104, + 30.505950163896036, + 33.993184984781542, + 37.408185128639695, + 40.772827853501868, + 44.100590565798301, + 47.400347780543231, + 50.678236946479898, + 53.93866620912693, + 57.184898598119301, + 60.419409852130297, + 63.644117508962281, + 66.860533012260103]] + +jnp_small_zeros = \ +[[0.0, + 3.8317059702075123, + 7.0155866698156188, + 10.173468135062722, + 13.323691936314223, + 16.470630050877633, + 19.615858510468242, + 22.760084380592772, + 25.903672087618383, + 29.046828534916855, + 32.189679910974404, + 35.332307550083865, + 38.474766234771615, + 41.617094212814451, + 44.759318997652822], + [1.8411837813406593, + 5.3314427735250326, + 8.5363163663462858, + 11.706004902592064, + 14.863588633909033, + 18.015527862681804, + 21.16436985918879, + 24.311326857210776, + 27.457050571059246, + 30.601922972669094, + 33.746182898667383, + 36.889987409236811, + 40.033444053350675, + 43.176628965448822, + 46.319597561173912], + [3.0542369282271403, + 6.7061331941584591, + 9.9694678230875958, + 13.170370856016123, + 16.347522318321783, + 19.512912782488205, + 22.671581772477426, + 25.826037141785263, + 28.977672772993679, + 32.127327020443474, + 35.275535050674691, + 38.422654817555906, + 41.568934936074314, + 44.714553532819734, + 47.859641607992093], + [4.2011889412105285, + 8.0152365983759522, + 11.345924310743006, + 14.585848286167028, + 17.78874786606647, + 20.9724769365377, + 24.144897432909265, + 27.310057930204349, + 30.470268806290424, + 33.626949182796679, + 36.781020675464386, + 39.933108623659488, + 43.083652662375079, + 46.232971081836478, + 49.381300092370349], + [5.3175531260839944, + 9.2823962852416123, + 12.681908442638891, + 15.964107037731551, + 19.196028800048905, + 22.401032267689004, + 25.589759681386733, + 28.767836217666503, + 31.938539340972783, + 35.103916677346764, + 38.265316987088158, + 41.423666498500732, + 44.579623137359257, + 47.733667523865744, + 50.886159153182682], + [6.4156163757002403, + 10.519860873772308, + 13.9871886301403, + 17.312842487884625, + 20.575514521386888, + 23.803581476593863, + 27.01030789777772, + 30.20284907898166, + 33.385443901010121, + 36.560777686880356, + 39.730640230067416, + 42.896273163494417, + 46.058566273567043, + 49.218174614666636, + 52.375591529563596], + [7.501266144684147, + 11.734935953042708, + 15.268181461097873, + 18.637443009666202, + 21.931715017802236, + 25.183925599499626, + 28.409776362510085, + 31.617875716105035, + 34.81339298429743, + 37.999640897715301, + 41.178849474321413, + 44.352579199070217, + 47.521956905768113, + 50.687817781723741, + 53.85079463676896], + [8.5778364897140741, + 12.932386237089576, + 16.529365884366944, + 19.941853366527342, + 23.268052926457571, + 26.545032061823576, + 29.790748583196614, + 33.015178641375142, + 36.224380548787162, + 39.422274578939259, + 42.611522172286684, + 45.793999658055002, + 48.971070951900596, + 52.143752969301988, + 55.312820330403446], + [9.6474216519972168, + 14.115518907894618, + 17.774012366915256, + 21.229062622853124, + 24.587197486317681, + 27.889269427955092, + 31.155326556188325, + 34.39662855427218, + 37.620078044197086, + 40.830178681822041, + 44.030010337966153, + 47.221758471887113, + 50.407020967034367, + 53.586995435398319, + 56.762598475105272], + [10.711433970699945, + 15.28673766733295, + 19.004593537946053, + 22.501398726777283, + 25.891277276839136, + 29.218563499936081, + 32.505247352375523, + 35.763792928808799, + 39.001902811514218, + 42.224638430753279, + 45.435483097475542, + 48.636922645305525, + 51.830783925834728, + 55.01844255063594, + 58.200955824859509], + [11.770876674955582, + 16.447852748486498, + 20.223031412681701, + 23.760715860327448, + 27.182021527190532, + 30.534504754007074, + 33.841965775135715, + 37.118000423665604, + 40.371068905333891, + 43.606764901379516, + 46.828959446564562, + 50.040428970943456, + 53.243223214220535, + 56.438892058982552, + 59.628631306921512], + [12.826491228033465, + 17.600266557468326, + 21.430854238060294, + 25.008518704644261, + 28.460857279654847, + 31.838424458616998, + 35.166714427392629, + 38.460388720328256, + 41.728625562624312, + 44.977526250903469, + 48.211333836373288, + 51.433105171422278, + 54.645106240447105, + 57.849056857839799, + 61.046288512821078], + [13.878843069697276, + 18.745090916814406, + 22.629300302835503, + 26.246047773946584, + 29.72897816891134, + 33.131449953571661, + 36.480548302231658, + 39.791940718940855, + 43.075486800191012, + 46.337772104541405, + 49.583396417633095, + 52.815686826850452, + 56.037118687012179, + 59.249577075517968, + 62.454525995970462], + [14.928374492964716, + 19.88322436109951, + 23.81938909003628, + 27.474339750968247, + 30.987394331665278, + 34.414545662167183, + 37.784378506209499, + 41.113512376883377, + 44.412454519229281, + 47.688252845993366, + 50.945849245830813, + 54.188831071035124, + 57.419876154678179, + 60.641030026538746, + 63.853885828967512], + [15.975438807484321, + 21.015404934568315, + 25.001971500138194, + 28.694271223110755, + 32.236969407878118, + 35.688544091185301, + 39.078998185245057, + 42.425854432866141, + 45.740236776624833, + 49.029635055514276, + 52.299319390331728, + 55.553127779547459, + 58.793933759028134, + 62.02393848337554, + 65.244860767043859]] + +yn_small_zeros = \ +[[0.89357696627916752, + 3.9576784193148579, + 7.0860510603017727, + 10.222345043496417, + 13.361097473872763, + 16.500922441528091, + 19.64130970088794, + 22.782028047291559, + 25.922957653180923, + 29.064030252728398, + 32.205204116493281, + 35.346452305214321, + 38.487756653081537, + 41.629104466213808, + 44.770486607221993], + [2.197141326031017, + 5.4296810407941351, + 8.5960058683311689, + 11.749154830839881, + 14.897442128336725, + 18.043402276727856, + 21.188068934142213, + 24.331942571356912, + 27.475294980449224, + 30.618286491641115, + 33.761017796109326, + 36.90355531614295, + 40.045944640266876, + 43.188218097393211, + 46.330399250701687], + [3.3842417671495935, + 6.7938075132682675, + 10.023477979360038, + 13.209986710206416, + 16.378966558947457, + 19.539039990286384, + 22.69395593890929, + 25.845613720902269, + 28.995080395650151, + 32.143002257627551, + 35.289793869635804, + 38.435733485446343, + 41.581014867297885, + 44.725777117640461, + 47.870122696676504], + [4.5270246611496439, + 8.0975537628604907, + 11.396466739595867, + 14.623077742393873, + 17.81845523294552, + 20.997284754187761, + 24.166235758581828, + 27.328799850405162, + 30.486989604098659, + 33.642049384702463, + 36.794791029185579, + 39.945767226378749, + 43.095367507846703, + 46.2438744334407, + 49.391498015725107], + [5.6451478942208959, + 9.3616206152445429, + 12.730144474090465, + 15.999627085382479, + 19.22442895931681, + 22.424810599698521, + 25.610267054939328, + 28.785893657666548, + 31.954686680031668, + 35.118529525584828, + 38.278668089521758, + 41.435960629910073, + 44.591018225353424, + 47.744288086361052, + 50.896105199722123], + [6.7471838248710219, + 10.597176726782031, + 14.033804104911233, + 17.347086393228382, + 20.602899017175335, + 23.826536030287532, + 27.030134937138834, + 30.220335654231385, + 33.401105611047908, + 36.574972486670962, + 39.743627733020277, + 42.908248189569535, + 46.069679073215439, + 49.228543693445843, + 52.385312123112282], + [7.8377378223268716, + 11.811037107609447, + 15.313615118517857, + 18.670704965906724, + 21.958290897126571, + 25.206207715021249, + 28.429037095235496, + 31.634879502950644, + 34.828638524084437, + 38.013473399691765, + 41.19151880917741, + 44.364272633271975, + 47.53281875312084, + 50.697961822183806, + 53.860312300118388], + [8.919605734873789, + 13.007711435388313, + 16.573915129085334, + 19.974342312352426, + 23.293972585596648, + 26.5667563757203, + 29.809531451608321, + 33.031769327150685, + 36.239265816598239, + 39.435790312675323, + 42.623910919472727, + 45.805442883111651, + 48.981708325514764, + 52.153694518185572, + 55.322154420959698], + [9.9946283820824834, + 14.190361295800141, + 17.817887841179873, + 21.26093227125945, + 24.612576377421522, + 27.910524883974868, + 31.173701563441602, + 34.412862242025045, + 37.634648706110989, + 40.843415321050884, + 44.04214994542435, + 47.232978012841169, + 50.417456447370186, + 53.596753874948731, + 56.771765754432457], + [11.064090256031013, + 15.361301343575925, + 19.047949646361388, + 22.532765416313869, + 25.91620496332662, + 29.2394205079349, + 32.523270869465881, + 35.779715464475261, + 39.016196664616095, + 42.237627509803703, + 45.4474001519274, + 48.647941127433196, + 51.841036928216499, + 55.028034667184916, + 58.209970905250097], + [12.128927704415439, + 16.522284394784426, + 20.265984501212254, + 23.791669719454272, + 27.206568881574774, + 30.555020011020762, + 33.859683872746356, + 37.133649760307504, + 40.385117593813002, + 43.619533085646856, + 46.840676630553575, + 50.051265851897857, + 53.253310556711732, + 56.448332488918971, + 59.637507005589829], + [13.189846995683845, + 17.674674253171487, + 21.473493977824902, + 25.03913093040942, + 28.485081336558058, + 31.858644293774859, + 35.184165245422787, + 38.475796636190897, + 41.742455848758449, + 44.990096293791186, + 48.222870660068338, + 51.443777308699826, + 54.655042589416311, + 57.858358441436511, + 61.055036135780528], + [14.247395665073945, + 18.819555894710682, + 22.671697117872794, + 26.276375544903892, + 29.752925495549038, + 33.151412708998983, + 36.497763772987645, + 39.807134090704376, + 43.089121522203808, + 46.350163579538652, + 49.594769786270069, + 52.82620892320143, + 56.046916910756961, + 59.258751140598783, + 62.463155567737854], + [15.30200785858925, + 19.957808654258601, + 23.861599172945054, + 27.504429642227545, + 31.011103429019229, + 34.434283425782942, + 37.801385632318459, + 41.128514139788358, + 44.425913324440663, + 47.700482714581842, + 50.957073905278458, + 54.199216028087261, + 57.429547607017405, + 60.65008661807661, + 63.862406280068586], + [16.354034360047551, + 21.090156519983806, + 25.044040298785627, + 28.724161640881914, + 32.260472459522644, + 35.708083982611664, + 39.095820003878235, + 42.440684315990936, + 45.75353669045622, + 49.041718113283529, + 52.310408280968073, + 55.56338698149062, + 58.803488508906895, + 62.032886550960831, + 65.253280088312461]] + +ynp_small_zeros = \ +[[2.197141326031017, + 5.4296810407941351, + 8.5960058683311689, + 11.749154830839881, + 14.897442128336725, + 18.043402276727856, + 21.188068934142213, + 24.331942571356912, + 27.475294980449224, + 30.618286491641115, + 33.761017796109326, + 36.90355531614295, + 40.045944640266876, + 43.188218097393211, + 46.330399250701687], + [3.6830228565851777, + 6.9414999536541757, + 10.123404655436613, + 13.285758156782854, + 16.440058007293282, + 19.590241756629495, + 22.738034717396327, + 25.884314618788867, + 29.029575819372535, + 32.174118233366201, + 35.318134458192094, + 38.461753870997549, + 41.605066618873108, + 44.74813744908079, + 47.891014070791065], + [5.0025829314460639, + 8.3507247014130795, + 11.574195465217647, + 14.760909306207676, + 17.931285939466855, + 21.092894504412739, + 24.249231678519058, + 27.402145837145258, + 30.552708880564553, + 33.70158627151572, + 36.849213419846257, + 39.995887376143356, + 43.141817835750686, + 46.287157097544201, + 49.432018469138281], + [6.2536332084598136, + 9.6987879841487711, + 12.972409052292216, + 16.19044719506921, + 19.38238844973613, + 22.559791857764261, + 25.728213194724094, + 28.890678419054777, + 32.048984005266337, + 35.204266606440635, + 38.357281675961019, + 41.508551443818436, + 44.658448731963676, + 47.807246956681162, + 50.95515126455207], + [7.4649217367571329, + 11.005169149809189, + 14.3317235192331, + 17.58443601710272, + 20.801062338411128, + 23.997004122902644, + 27.179886689853435, + 30.353960608554323, + 33.521797098666792, + 36.685048382072301, + 39.844826969405863, + 43.001910515625288, + 46.15685955107263, + 49.310088614282257, + 52.461911043685864], + [8.6495562436971983, + 12.280868725807848, + 15.660799304540377, + 18.949739756016503, + 22.192841809428241, + 25.409072788867674, + 28.608039283077593, + 31.795195353138159, + 34.973890634255288, + 38.14630522169358, + 41.313923188794905, + 44.477791768537617, + 47.638672065035628, + 50.797131066967842, + 53.953600129601663], + [9.8147970120105779, + 13.532811875789828, + 16.965526446046053, + 20.291285512443867, + 23.56186260680065, + 26.799499736027237, + 30.015665481543419, + 33.216968050039509, + 36.407516858984748, + 39.590015243560459, + 42.766320595957378, + 45.937754257017323, + 49.105283450953203, + 52.269633324547373, + 55.431358715604255], + [10.965152105242974, + 14.765687379508912, + 18.250123150217555, + 21.612750053384621, + 24.911310600813573, + 28.171051927637585, + 31.40518108895689, + 34.621401012564177, + 37.824552065973114, + 41.017847386464902, + 44.203512240871601, + 47.3831408366063, + 50.557907466622796, + 53.728697478957026, + 56.896191727313342], + [12.103641941939539, + 15.982840905145284, + 19.517731005559611, + 22.916962141504605, + 26.243700855690533, + 29.525960140695407, + 32.778568197561124, + 36.010261572392516, + 39.226578757802172, + 42.43122493258747, + 45.626783824134354, + 48.815117837929515, + 51.997606404328863, + 55.175294723956816, + 58.348990221754937], + [13.232403808592215, + 17.186756572616758, + 20.770762917490496, + 24.206152448722253, + 27.561059462697153, + 30.866053571250639, + 34.137476603379774, + 37.385039772270268, + 40.614946085165892, + 43.831373184731238, + 47.037251786726299, + 50.234705848765229, + 53.425316228549359, + 56.610286079882087, + 59.790548623216652], + [14.35301374369987, + 18.379337301642568, + 22.011118775283494, + 25.482116178696707, + 28.865046588695164, + 32.192853922166294, + 35.483296655830277, + 38.747005493021857, + 41.990815194320955, + 45.219355876831731, + 48.435892856078888, + 51.642803925173029, + 54.84186659475857, + 58.034439083840155, + 61.221578745109862], + [15.466672066554263, + 19.562077985759503, + 23.240325531101082, + 26.746322986645901, + 30.157042415639891, + 33.507642948240263, + 36.817212798512775, + 40.097251300178642, + 43.355193847719752, + 46.596103410173672, + 49.823567279972794, + 53.040208868780832, + 56.247996968470062, + 59.448441365714251, + 62.642721301357187], + [16.574317035530872, + 20.73617763753932, + 24.459631728238804, + 27.999993668839644, + 31.438208790267783, + 34.811512070805535, + 38.140243708611251, + 41.436725143893739, + 44.708963264433333, + 47.962435051891027, + 51.201037321915983, + 54.427630745992975, + 57.644369734615238, + 60.852911791989989, + 64.054555435720397], + [17.676697936439624, + 21.9026148697762, + 25.670073356263225, + 29.244155124266438, + 32.709534477396028, + 36.105399554497548, + 39.453272918267025, + 42.766255701958017, + 46.052899215578358, + 49.319076602061401, + 52.568982147952547, + 55.805705507386287, + 59.031580956740466, + 62.248409689597653, + 65.457606670836759], + [18.774423978290318, + 23.06220035979272, + 26.872520985976736, + 30.479680663499762, + 33.971869047372436, + 37.390118854896324, + 40.757072537673599, + 44.086572292170345, + 47.387688809191869, + 50.66667461073936, + 53.928009929563275, + 57.175005343085052, + 60.410169281219877, + 63.635442539153021, + 66.85235358587768]] + +@pytest.mark.slow +def test_bessel_zeros_extra(): + mp.dps = 15 + for v in range(V): + for m in range(1,M+1): + print(v, m, "of", V, M) + # Twice to test cache (if used) + assert besseljzero(v,m).ae(jn_small_zeros[v][m-1]) + assert besseljzero(v,m).ae(jn_small_zeros[v][m-1]) + assert besseljzero(v,m,1).ae(jnp_small_zeros[v][m-1]) + assert besseljzero(v,m,1).ae(jnp_small_zeros[v][m-1]) + assert besselyzero(v,m).ae(yn_small_zeros[v][m-1]) + assert besselyzero(v,m).ae(yn_small_zeros[v][m-1]) + assert besselyzero(v,m,1).ae(ynp_small_zeros[v][m-1]) + assert besselyzero(v,m,1).ae(ynp_small_zeros[v][m-1]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_gammazeta.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_gammazeta.py new file mode 100644 index 0000000000000000000000000000000000000000..6a18a7964d746561dfd5f81177cd78ccc46d2a5d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_gammazeta.py @@ -0,0 +1,698 @@ +from mpmath import * +from mpmath.libmp import round_up, from_float, mpf_zeta_int + +def test_zeta_int_bug(): + assert mpf_zeta_int(0, 10) == from_float(-0.5) + +def test_bernoulli(): + assert bernfrac(0) == (1,1) + assert bernfrac(1) == (-1,2) + assert bernfrac(2) == (1,6) + assert bernfrac(3) == (0,1) + assert bernfrac(4) == (-1,30) + assert bernfrac(5) == (0,1) + assert bernfrac(6) == (1,42) + assert bernfrac(8) == (-1,30) + assert bernfrac(10) == (5,66) + assert bernfrac(12) == (-691,2730) + assert bernfrac(18) == (43867,798) + p, q = bernfrac(228) + assert p % 10**10 == 164918161 + assert q == 625170 + p, q = bernfrac(1000) + assert p % 10**10 == 7950421099 + assert q == 342999030 + mp.dps = 15 + assert bernoulli(0) == 1 + assert bernoulli(1) == -0.5 + assert bernoulli(2).ae(1./6) + assert bernoulli(3) == 0 + assert bernoulli(4).ae(-1./30) + assert bernoulli(5) == 0 + assert bernoulli(6).ae(1./42) + assert str(bernoulli(10)) == '0.0757575757575758' + assert str(bernoulli(234)) == '7.62772793964344e+267' + assert str(bernoulli(10**5)) == '-5.82229431461335e+376755' + assert str(bernoulli(10**8+2)) == '1.19570355039953e+676752584' + + mp.dps = 50 + assert str(bernoulli(10)) == '0.075757575757575757575757575757575757575757575757576' + assert str(bernoulli(234)) == '7.6277279396434392486994969020496121553385863373331e+267' + assert str(bernoulli(10**5)) == '-5.8222943146133508236497045360612887555320691004308e+376755' + assert str(bernoulli(10**8+2)) == '1.1957035503995297272263047884604346914602088317782e+676752584' + + mp.dps = 1000 + assert bernoulli(10).ae(mpf(5)/66) + + mp.dps = 50000 + assert bernoulli(10).ae(mpf(5)/66) + + mp.dps = 15 + +def test_bernpoly_eulerpoly(): + mp.dps = 15 + assert bernpoly(0,-1).ae(1) + assert bernpoly(0,0).ae(1) + assert bernpoly(0,'1/2').ae(1) + assert bernpoly(0,'3/4').ae(1) + assert bernpoly(0,1).ae(1) + assert bernpoly(0,2).ae(1) + assert bernpoly(1,-1).ae('-3/2') + assert bernpoly(1,0).ae('-1/2') + assert bernpoly(1,'1/2').ae(0) + assert bernpoly(1,'3/4').ae('1/4') + assert bernpoly(1,1).ae('1/2') + assert bernpoly(1,2).ae('3/2') + assert bernpoly(2,-1).ae('13/6') + assert bernpoly(2,0).ae('1/6') + assert bernpoly(2,'1/2').ae('-1/12') + assert bernpoly(2,'3/4').ae('-1/48') + assert bernpoly(2,1).ae('1/6') + assert bernpoly(2,2).ae('13/6') + assert bernpoly(3,-1).ae(-3) + assert bernpoly(3,0).ae(0) + assert bernpoly(3,'1/2').ae(0) + assert bernpoly(3,'3/4').ae('-3/64') + assert bernpoly(3,1).ae(0) + assert bernpoly(3,2).ae(3) + assert bernpoly(4,-1).ae('119/30') + assert bernpoly(4,0).ae('-1/30') + assert bernpoly(4,'1/2').ae('7/240') + assert bernpoly(4,'3/4').ae('7/3840') + assert bernpoly(4,1).ae('-1/30') + assert bernpoly(4,2).ae('119/30') + assert bernpoly(5,-1).ae(-5) + assert bernpoly(5,0).ae(0) + assert bernpoly(5,'1/2').ae(0) + assert bernpoly(5,'3/4').ae('25/1024') + assert bernpoly(5,1).ae(0) + assert bernpoly(5,2).ae(5) + assert bernpoly(10,-1).ae('665/66') + assert bernpoly(10,0).ae('5/66') + assert bernpoly(10,'1/2').ae('-2555/33792') + assert bernpoly(10,'3/4').ae('-2555/34603008') + assert bernpoly(10,1).ae('5/66') + assert bernpoly(10,2).ae('665/66') + assert bernpoly(11,-1).ae(-11) + assert bernpoly(11,0).ae(0) + assert bernpoly(11,'1/2').ae(0) + assert bernpoly(11,'3/4').ae('-555731/4194304') + assert bernpoly(11,1).ae(0) + assert bernpoly(11,2).ae(11) + assert eulerpoly(0,-1).ae(1) + assert eulerpoly(0,0).ae(1) + assert eulerpoly(0,'1/2').ae(1) + assert eulerpoly(0,'3/4').ae(1) + assert eulerpoly(0,1).ae(1) + assert eulerpoly(0,2).ae(1) + assert eulerpoly(1,-1).ae('-3/2') + assert eulerpoly(1,0).ae('-1/2') + assert eulerpoly(1,'1/2').ae(0) + assert eulerpoly(1,'3/4').ae('1/4') + assert eulerpoly(1,1).ae('1/2') + assert eulerpoly(1,2).ae('3/2') + assert eulerpoly(2,-1).ae(2) + assert eulerpoly(2,0).ae(0) + assert eulerpoly(2,'1/2').ae('-1/4') + assert eulerpoly(2,'3/4').ae('-3/16') + assert eulerpoly(2,1).ae(0) + assert eulerpoly(2,2).ae(2) + assert eulerpoly(3,-1).ae('-9/4') + assert eulerpoly(3,0).ae('1/4') + assert eulerpoly(3,'1/2').ae(0) + assert eulerpoly(3,'3/4').ae('-11/64') + assert eulerpoly(3,1).ae('-1/4') + assert eulerpoly(3,2).ae('9/4') + assert eulerpoly(4,-1).ae(2) + assert eulerpoly(4,0).ae(0) + assert eulerpoly(4,'1/2').ae('5/16') + assert eulerpoly(4,'3/4').ae('57/256') + assert eulerpoly(4,1).ae(0) + assert eulerpoly(4,2).ae(2) + assert eulerpoly(5,-1).ae('-3/2') + assert eulerpoly(5,0).ae('-1/2') + assert eulerpoly(5,'1/2').ae(0) + assert eulerpoly(5,'3/4').ae('361/1024') + assert eulerpoly(5,1).ae('1/2') + assert eulerpoly(5,2).ae('3/2') + assert eulerpoly(10,-1).ae(2) + assert eulerpoly(10,0).ae(0) + assert eulerpoly(10,'1/2').ae('-50521/1024') + assert eulerpoly(10,'3/4').ae('-36581523/1048576') + assert eulerpoly(10,1).ae(0) + assert eulerpoly(10,2).ae(2) + assert eulerpoly(11,-1).ae('-699/4') + assert eulerpoly(11,0).ae('691/4') + assert eulerpoly(11,'1/2').ae(0) + assert eulerpoly(11,'3/4').ae('-512343611/4194304') + assert eulerpoly(11,1).ae('-691/4') + assert eulerpoly(11,2).ae('699/4') + # Potential accuracy issues + assert bernpoly(10000,10000).ae('5.8196915936323387117e+39999') + assert bernpoly(200,17.5).ae(3.8048418524583064909e244) + assert eulerpoly(200,17.5).ae(-3.7309911582655785929e275) + +def test_gamma(): + mp.dps = 15 + assert gamma(0.25).ae(3.6256099082219083119) + assert gamma(0.0001).ae(9999.4228832316241908) + assert gamma(300).ae('1.0201917073881354535e612') + assert gamma(-0.5).ae(-3.5449077018110320546) + assert gamma(-7.43).ae(0.00026524416464197007186) + #assert gamma(Rational(1,2)) == gamma(0.5) + #assert gamma(Rational(-7,3)).ae(gamma(mpf(-7)/3)) + assert gamma(1+1j).ae(0.49801566811835604271 - 0.15494982830181068512j) + assert gamma(-1+0.01j).ae(-0.422733904013474115 + 99.985883082635367436j) + assert gamma(20+30j).ae(-1453876687.5534810 + 1163777777.8031573j) + # Should always give exact factorials when they can + # be represented as mpfs under the current working precision + fact = 1 + for i in range(1, 18): + assert gamma(i) == fact + fact *= i + for dps in [170, 600]: + fact = 1 + mp.dps = dps + for i in range(1, 105): + assert gamma(i) == fact + fact *= i + mp.dps = 100 + assert gamma(0.5).ae(sqrt(pi)) + mp.dps = 15 + assert factorial(0) == fac(0) == 1 + assert factorial(3) == 6 + assert isnan(gamma(nan)) + assert gamma(1100).ae('4.8579168073569433667e2866') + assert rgamma(0) == 0 + assert rgamma(-1) == 0 + assert rgamma(2) == 1.0 + assert rgamma(3) == 0.5 + assert loggamma(2+8j).ae(-8.5205176753667636926 + 10.8569497125597429366j) + assert loggamma('1e10000').ae('2.302485092994045684017991e10004') + assert loggamma('1e10000j').ae(mpc('-1.570796326794896619231322e10000','2.302485092994045684017991e10004')) + +def test_fac2(): + mp.dps = 15 + assert [fac2(n) for n in range(10)] == [1,1,2,3,8,15,48,105,384,945] + assert fac2(-5).ae(1./3) + assert fac2(-11).ae(-1./945) + assert fac2(50).ae(5.20469842636666623e32) + assert fac2(0.5+0.75j).ae(0.81546769394688069176-0.34901016085573266889j) + assert fac2(inf) == inf + assert isnan(fac2(-inf)) + +def test_gamma_quotients(): + mp.dps = 15 + h = 1e-8 + ep = 1e-4 + G = gamma + assert gammaprod([-1],[-3,-4]) == 0 + assert gammaprod([-1,0],[-5]) == inf + assert abs(gammaprod([-1],[-2]) - G(-1+h)/G(-2+h)) < 1e-4 + assert abs(gammaprod([-4,-3],[-2,0]) - G(-4+h)*G(-3+h)/G(-2+h)/G(0+h)) < 1e-4 + assert rf(3,0) == 1 + assert rf(2.5,1) == 2.5 + assert rf(-5,2) == 20 + assert rf(j,j).ae(gamma(2*j)/gamma(j)) + assert rf('-255.5815971722918','-0.5119253100282322').ae('-0.1952720278805729485') # issue 421 + assert ff(-2,0) == 1 + assert ff(-2,1) == -2 + assert ff(4,3) == 24 + assert ff(3,4) == 0 + assert binomial(0,0) == 1 + assert binomial(1,0) == 1 + assert binomial(0,-1) == 0 + assert binomial(3,2) == 3 + assert binomial(5,2) == 10 + assert binomial(5,3) == 10 + assert binomial(5,5) == 1 + assert binomial(-1,0) == 1 + assert binomial(-2,-4) == 3 + assert binomial(4.5, 1.5) == 6.5625 + assert binomial(1100,1) == 1100 + assert binomial(1100,2) == 604450 + assert beta(1,1) == 1 + assert beta(0,0) == inf + assert beta(3,0) == inf + assert beta(-1,-1) == inf + assert beta(1.5,1).ae(2/3.) + assert beta(1.5,2.5).ae(pi/16) + assert (10**15*beta(10,100)).ae(2.3455339739604649879) + assert beta(inf,inf) == 0 + assert isnan(beta(-inf,inf)) + assert isnan(beta(-3,inf)) + assert isnan(beta(0,inf)) + assert beta(inf,0.5) == beta(0.5,inf) == 0 + assert beta(inf,-1.5) == inf + assert beta(inf,-0.5) == -inf + assert beta(1+2j,-1-j/2).ae(1.16396542451069943086+0.08511695947832914640j) + assert beta(-0.5,0.5) == 0 + assert beta(-3,3).ae(-1/3.) + assert beta('-255.5815971722918','-0.5119253100282322').ae('18.157330562703710339') # issue 421 + +def test_zeta(): + mp.dps = 15 + assert zeta(2).ae(pi**2 / 6) + assert zeta(2.0).ae(pi**2 / 6) + assert zeta(mpc(2)).ae(pi**2 / 6) + assert zeta(100).ae(1) + assert zeta(0).ae(-0.5) + assert zeta(0.5).ae(-1.46035450880958681) + assert zeta(-1).ae(-mpf(1)/12) + assert zeta(-2) == 0 + assert zeta(-3).ae(mpf(1)/120) + assert zeta(-4) == 0 + assert zeta(-100) == 0 + assert isnan(zeta(nan)) + assert zeta(1e-30).ae(-0.5) + assert zeta(-1e-30).ae(-0.5) + # Zeros in the critical strip + assert zeta(mpc(0.5, 14.1347251417346937904)).ae(0) + assert zeta(mpc(0.5, 21.0220396387715549926)).ae(0) + assert zeta(mpc(0.5, 25.0108575801456887632)).ae(0) + assert zeta(mpc(1e-30,1e-40)).ae(-0.5) + assert zeta(mpc(-1e-30,1e-40)).ae(-0.5) + mp.dps = 50 + im = '236.5242296658162058024755079556629786895294952121891237' + assert zeta(mpc(0.5, im)).ae(0, 1e-46) + mp.dps = 15 + # Complex reflection formula + assert (zeta(-60+3j) / 10**34).ae(8.6270183987866146+15.337398548226238j) + # issue #358 + assert zeta(0,0.5) == 0 + assert zeta(0,0) == 0.5 + assert zeta(0,0.5,1).ae(-0.34657359027997265) + # see issue #390 + assert zeta(-1.5,0.5j).ae(-0.13671400162512768475 + 0.11411333638426559139j) + +def test_altzeta(): + mp.dps = 15 + assert altzeta(-2) == 0 + assert altzeta(-4) == 0 + assert altzeta(-100) == 0 + assert altzeta(0) == 0.5 + assert altzeta(-1) == 0.25 + assert altzeta(-3) == -0.125 + assert altzeta(-5) == 0.25 + assert altzeta(-21) == 1180529130.25 + assert altzeta(1).ae(log(2)) + assert altzeta(2).ae(pi**2/12) + assert altzeta(10).ae(73*pi**10/6842880) + assert altzeta(50) < 1 + assert altzeta(60, rounding='d') < 1 + assert altzeta(60, rounding='u') == 1 + assert altzeta(10000, rounding='d') < 1 + assert altzeta(10000, rounding='u') == 1 + assert altzeta(3+0j) == altzeta(3) + s = 3+4j + assert altzeta(s).ae((1-2**(1-s))*zeta(s)) + s = -3+4j + assert altzeta(s).ae((1-2**(1-s))*zeta(s)) + assert altzeta(-100.5).ae(4.58595480083585913e+108) + assert altzeta(1.3).ae(0.73821404216623045) + assert altzeta(1e-30).ae(0.5) + assert altzeta(-1e-30).ae(0.5) + assert altzeta(mpc(1e-30,1e-40)).ae(0.5) + assert altzeta(mpc(-1e-30,1e-40)).ae(0.5) + +def test_zeta_huge(): + mp.dps = 15 + assert zeta(inf) == 1 + mp.dps = 50 + assert zeta(100).ae('1.0000000000000000000000000000007888609052210118073522') + assert zeta(40*pi).ae('1.0000000000000000000000000000000000000148407238666182') + mp.dps = 10000 + v = zeta(33000) + mp.dps = 15 + assert str(v-1) == '1.02363019598118e-9934' + assert zeta(pi*1000, rounding=round_up) > 1 + assert zeta(3000, rounding=round_up) > 1 + assert zeta(pi*1000) == 1 + assert zeta(3000) == 1 + +def test_zeta_negative(): + mp.dps = 150 + a = -pi*10**40 + mp.dps = 15 + assert str(zeta(a)) == '2.55880492708712e+1233536161668617575553892558646631323374078' + mp.dps = 50 + assert str(zeta(a)) == '2.5588049270871154960875033337384432038436330847333e+1233536161668617575553892558646631323374078' + mp.dps = 15 + +def test_polygamma(): + mp.dps = 15 + psi0 = lambda z: psi(0,z) + psi1 = lambda z: psi(1,z) + assert psi0(3) == psi(0,3) == digamma(3) + #assert psi2(3) == psi(2,3) == tetragamma(3) + #assert psi3(3) == psi(3,3) == pentagamma(3) + assert psi0(pi).ae(0.97721330794200673) + assert psi0(-pi).ae(7.8859523853854902) + assert psi0(-pi+1).ae(7.5676424992016996) + assert psi0(pi+j).ae(1.04224048313859376 + 0.35853686544063749j) + assert psi0(-pi-j).ae(1.3404026194821986 - 2.8824392476809402j) + assert findroot(psi0, 1).ae(1.4616321449683622) + assert psi0(1e-10).ae(-10000000000.57722) + assert psi0(1e-40).ae(-1.000000000000000e+40) + assert psi0(1e-10+1e-10j).ae(-5000000000.577215 + 5000000000.000000j) + assert psi0(1e-40+1e-40j).ae(-5.000000000000000e+39 + 5.000000000000000e+39j) + assert psi0(inf) == inf + assert psi1(inf) == 0 + assert psi(2,inf) == 0 + assert psi1(pi).ae(0.37424376965420049) + assert psi1(-pi).ae(53.030438740085385) + assert psi1(pi+j).ae(0.32935710377142464 - 0.12222163911221135j) + assert psi1(-pi-j).ae(-0.30065008356019703 + 0.01149892486928227j) + assert (10**6*psi(4,1+10*pi*j)).ae(-6.1491803479004446 - 0.3921316371664063j) + assert psi0(1+10*pi*j).ae(3.4473994217222650 + 1.5548808324857071j) + assert isnan(psi0(nan)) + assert isnan(psi0(-inf)) + assert psi0(-100.5).ae(4.615124601338064) + assert psi0(3+0j).ae(psi0(3)) + assert psi0(-100+3j).ae(4.6106071768714086321+3.1117510556817394626j) + assert isnan(psi(2,mpc(0,inf))) + assert isnan(psi(2,mpc(0,nan))) + assert isnan(psi(2,mpc(0,-inf))) + assert isnan(psi(2,mpc(1,inf))) + assert isnan(psi(2,mpc(1,nan))) + assert isnan(psi(2,mpc(1,-inf))) + assert isnan(psi(2,mpc(inf,inf))) + assert isnan(psi(2,mpc(nan,nan))) + assert isnan(psi(2,mpc(-inf,-inf))) + mp.dps = 30 + # issue #534 + assert digamma(-0.75+1j).ae(mpc('0.46317279488182026118963809283042317', '2.4821070143037957102007677817351115')) + mp.dps = 15 + +def test_polygamma_high_prec(): + mp.dps = 100 + assert str(psi(0,pi)) == "0.9772133079420067332920694864061823436408346099943256380095232865318105924777141317302075654362928734" + assert str(psi(10,pi)) == "-12.98876181434889529310283769414222588307175962213707170773803550518307617769657562747174101900659238" + +def test_polygamma_identities(): + mp.dps = 15 + psi0 = lambda z: psi(0,z) + psi1 = lambda z: psi(1,z) + psi2 = lambda z: psi(2,z) + assert psi0(0.5).ae(-euler-2*log(2)) + assert psi0(1).ae(-euler) + assert psi1(0.5).ae(0.5*pi**2) + assert psi1(1).ae(pi**2/6) + assert psi1(0.25).ae(pi**2 + 8*catalan) + assert psi2(1).ae(-2*apery) + mp.dps = 20 + u = -182*apery+4*sqrt(3)*pi**3 + mp.dps = 15 + assert psi(2,5/6.).ae(u) + assert psi(3,0.5).ae(pi**4) + +def test_foxtrot_identity(): + # A test of the complex digamma function. + # See http://mathworld.wolfram.com/FoxTrotSeries.html and + # http://mathworld.wolfram.com/DigammaFunction.html + psi0 = lambda z: psi(0,z) + mp.dps = 50 + a = (-1)**fraction(1,3) + b = (-1)**fraction(2,3) + x = -psi0(0.5*a) - psi0(-0.5*b) + psi0(0.5*(1+a)) + psi0(0.5*(1-b)) + y = 2*pi*sech(0.5*sqrt(3)*pi) + assert x.ae(y) + mp.dps = 15 + +def test_polygamma_high_order(): + mp.dps = 100 + assert str(psi(50, pi)) == "-1344100348958402765749252447726432491812.641985273160531055707095989227897753035823152397679626136483" + assert str(psi(50, pi + 14*e)) == "-0.00000000000000000189793739550804321623512073101895801993019919886375952881053090844591920308111549337295143780341396" + assert str(psi(50, pi + 14*e*j)) == ("(-0.0000000000000000522516941152169248975225472155683565752375889510631513244785" + "9377385233700094871256507814151956624433 - 0.00000000000000001813157041407010184" + "702414110218205348527862196327980417757665282244728963891298080199341480881811613j)") + mp.dps = 15 + assert str(psi(50, pi)) == "-1.34410034895841e+39" + assert str(psi(50, pi + 14*e)) == "-1.89793739550804e-18" + assert str(psi(50, pi + 14*e*j)) == "(-5.2251694115217e-17 - 1.81315704140701e-17j)" + +def test_harmonic(): + mp.dps = 15 + assert harmonic(0) == 0 + assert harmonic(1) == 1 + assert harmonic(2) == 1.5 + assert harmonic(3).ae(1. + 1./2 + 1./3) + assert harmonic(10**10).ae(23.603066594891989701) + assert harmonic(10**1000).ae(2303.162308658947) + assert harmonic(0.5).ae(2-2*log(2)) + assert harmonic(inf) == inf + assert harmonic(2+0j) == 1.5+0j + assert harmonic(1+2j).ae(1.4918071802755104+0.92080728264223022j) + +def test_gamma_huge_1(): + mp.dps = 500 + x = mpf(10**10) / 7 + mp.dps = 15 + assert str(gamma(x)) == "6.26075321389519e+12458010678" + mp.dps = 50 + assert str(gamma(x)) == "6.2607532138951929201303779291707455874010420783933e+12458010678" + mp.dps = 15 + +def test_gamma_huge_2(): + mp.dps = 500 + x = mpf(10**100) / 19 + mp.dps = 15 + assert str(gamma(x)) == (\ + "1.82341134776679e+5172997469323364168990133558175077136829182824042201886051511" + "9656908623426021308685461258226190190661") + mp.dps = 50 + assert str(gamma(x)) == (\ + "1.82341134776678875374414910350027596939980412984e+5172997469323364168990133558" + "1750771368291828240422018860515119656908623426021308685461258226190190661") + +def test_gamma_huge_3(): + mp.dps = 500 + x = 10**80 // 3 + 10**70*j / 7 + mp.dps = 15 + y = gamma(x) + assert str(y.real) == (\ + "-6.82925203918106e+2636286142112569524501781477865238132302397236429627932441916" + "056964386399485392600") + assert str(y.imag) == (\ + "8.54647143678418e+26362861421125695245017814778652381323023972364296279324419160" + "56964386399485392600") + mp.dps = 50 + y = gamma(x) + assert str(y.real) == (\ + "-6.8292520391810548460682736226799637356016538421817e+26362861421125695245017814" + "77865238132302397236429627932441916056964386399485392600") + assert str(y.imag) == (\ + "8.5464714367841748507479306948130687511711420234015e+263628614211256952450178147" + "7865238132302397236429627932441916056964386399485392600") + +def test_gamma_huge_4(): + x = 3200+11500j + mp.dps = 15 + assert str(gamma(x)) == \ + "(8.95783268539713e+5164 - 1.94678798329735e+5164j)" + mp.dps = 50 + assert str(gamma(x)) == (\ + "(8.9578326853971339570292952697675570822206567327092e+5164" + " - 1.9467879832973509568895402139429643650329524144794e+51" + "64j)") + mp.dps = 15 + +def test_gamma_huge_5(): + mp.dps = 500 + x = 10**60 * j / 3 + mp.dps = 15 + y = gamma(x) + assert str(y.real) == "-3.27753899634941e-227396058973640224580963937571892628368354580620654233316839" + assert str(y.imag) == "-7.1519888950416e-227396058973640224580963937571892628368354580620654233316841" + mp.dps = 50 + y = gamma(x) + assert str(y.real) == (\ + "-3.2775389963494132168950056995974690946983219123935e-22739605897364022458096393" + "7571892628368354580620654233316839") + assert str(y.imag) == (\ + "-7.1519888950415979749736749222530209713136588885897e-22739605897364022458096393" + "7571892628368354580620654233316841") + mp.dps = 15 + +def test_gamma_huge_7(): + mp.dps = 100 + a = 3 + j/mpf(10)**1000 + mp.dps = 15 + y = gamma(a) + assert str(y.real) == "2.0" + # wrong + #assert str(y.imag) == "2.16735365342606e-1000" + assert str(y.imag) == "1.84556867019693e-1000" + mp.dps = 50 + y = gamma(a) + assert str(y.real) == "2.0" + #assert str(y.imag) == "2.1673536534260596065418805612488708028522563689298e-1000" + assert str(y.imag) == "1.8455686701969342787869758198351951379156813281202e-1000" + +def test_stieltjes(): + mp.dps = 15 + assert stieltjes(0).ae(+euler) + mp.dps = 25 + assert stieltjes(1).ae('-0.07281584548367672486058637587') + assert stieltjes(2).ae('-0.009690363192872318484530386035') + assert stieltjes(3).ae('0.002053834420303345866160046543') + assert stieltjes(4).ae('0.002325370065467300057468170178') + mp.dps = 15 + assert stieltjes(1).ae(-0.07281584548367672486058637587) + assert stieltjes(2).ae(-0.009690363192872318484530386035) + assert stieltjes(3).ae(0.002053834420303345866160046543) + assert stieltjes(4).ae(0.0023253700654673000574681701775) + +def test_barnesg(): + mp.dps = 15 + assert barnesg(0) == barnesg(-1) == 0 + assert [superfac(i) for i in range(8)] == [1, 1, 2, 12, 288, 34560, 24883200, 125411328000] + assert str(superfac(1000)) == '3.24570818422368e+1177245' + assert isnan(barnesg(nan)) + assert isnan(superfac(nan)) + assert isnan(hyperfac(nan)) + assert barnesg(inf) == inf + assert superfac(inf) == inf + assert hyperfac(inf) == inf + assert isnan(superfac(-inf)) + assert barnesg(0.7).ae(0.8068722730141471) + assert barnesg(2+3j).ae(-0.17810213864082169+0.04504542715447838j) + assert [hyperfac(n) for n in range(7)] == [1, 1, 4, 108, 27648, 86400000, 4031078400000] + assert [hyperfac(n) for n in range(0,-7,-1)] == [1,1,-1,-4,108,27648,-86400000] + a = barnesg(-3+0j) + assert a == 0 and isinstance(a, mpc) + a = hyperfac(-3+0j) + assert a == -4 and isinstance(a, mpc) + +def test_polylog(): + mp.dps = 15 + zs = [mpmathify(z) for z in [0, 0.5, 0.99, 4, -0.5, -4, 1j, 3+4j]] + for z in zs: assert polylog(1, z).ae(-log(1-z)) + for z in zs: assert polylog(0, z).ae(z/(1-z)) + for z in zs: assert polylog(-1, z).ae(z/(1-z)**2) + for z in zs: assert polylog(-2, z).ae(z*(1+z)/(1-z)**3) + for z in zs: assert polylog(-3, z).ae(z*(1+4*z+z**2)/(1-z)**4) + assert polylog(3, 7).ae(5.3192579921456754382-5.9479244480803301023j) + assert polylog(3, -7).ae(-4.5693548977219423182) + assert polylog(2, 0.9).ae(1.2997147230049587252) + assert polylog(2, -0.9).ae(-0.75216317921726162037) + assert polylog(2, 0.9j).ae(-0.17177943786580149299+0.83598828572550503226j) + assert polylog(2, 1.1).ae(1.9619991013055685931-0.2994257606855892575j) + assert polylog(2, -1.1).ae(-0.89083809026228260587) + assert polylog(2, 1.1*sqrt(j)).ae(0.58841571107611387722+1.09962542118827026011j) + assert polylog(-2, 0.9).ae(1710) + assert polylog(-2, -0.9).ae(-90/6859.) + assert polylog(3, 0.9).ae(1.0496589501864398696) + assert polylog(-3, 0.9).ae(48690) + assert polylog(-3, -4).ae(-0.0064) + assert polylog(0.5+j/3, 0.5+j/2).ae(0.31739144796565650535 + 0.99255390416556261437j) + assert polylog(3+4j,1).ae(zeta(3+4j)) + assert polylog(3+4j,-1).ae(-altzeta(3+4j)) + # issue 390 + assert polylog(1.5, -48.910886523731889).ae(-6.272992229311817) + assert polylog(1.5, 200).ae(-8.349608319033686529 - 8.159694826434266042j) + assert polylog(-2+0j, -2).ae(mpf(1)/13.5) + assert polylog(-2+0j, 1.25).ae(-180) + +def test_bell_polyexp(): + mp.dps = 15 + # TODO: more tests for polyexp + assert (polyexp(0,1e-10)*10**10).ae(1.00000000005) + assert (polyexp(1,1e-10)*10**10).ae(1.0000000001) + assert polyexp(5,3j).ae(-607.7044517476176454+519.962786482001476087j) + assert polyexp(-1,3.5).ae(12.09537536175543444) + # bell(0,x) = 1 + assert bell(0,0) == 1 + assert bell(0,1) == 1 + assert bell(0,2) == 1 + assert bell(0,inf) == 1 + assert bell(0,-inf) == 1 + assert isnan(bell(0,nan)) + # bell(1,x) = x + assert bell(1,4) == 4 + assert bell(1,0) == 0 + assert bell(1,inf) == inf + assert bell(1,-inf) == -inf + assert isnan(bell(1,nan)) + # bell(2,x) = x*(1+x) + assert bell(2,-1) == 0 + assert bell(2,0) == 0 + # large orders / arguments + assert bell(10) == 115975 + assert bell(10,1) == 115975 + assert bell(10, -8) == 11054008 + assert bell(5,-50) == -253087550 + assert bell(50,-50).ae('3.4746902914629720259e74') + mp.dps = 80 + assert bell(50,-50) == 347469029146297202586097646631767227177164818163463279814268368579055777450 + assert bell(40,50) == 5575520134721105844739265207408344706846955281965031698187656176321717550 + assert bell(74) == 5006908024247925379707076470957722220463116781409659160159536981161298714301202 + mp.dps = 15 + assert bell(10,20j) == 7504528595600+15649605360020j + # continuity of the generalization + assert bell(0.5,0).ae(sinc(pi*0.5)) + +def test_primezeta(): + mp.dps = 15 + assert primezeta(0.9).ae(1.8388316154446882243 + 3.1415926535897932385j) + assert primezeta(4).ae(0.076993139764246844943) + assert primezeta(1) == inf + assert primezeta(inf) == 0 + assert isnan(primezeta(nan)) + +def test_rs_zeta(): + mp.dps = 15 + assert zeta(0.5+100000j).ae(1.0730320148577531321 + 5.7808485443635039843j) + assert zeta(0.75+100000j).ae(1.837852337251873704 + 1.9988492668661145358j) + assert zeta(0.5+1000000j, derivative=3).ae(1647.7744105852674733 - 1423.1270943036622097j) + assert zeta(1+1000000j, derivative=3).ae(3.4085866124523582894 - 18.179184721525947301j) + assert zeta(1+1000000j, derivative=1).ae(-0.10423479366985452134 - 0.74728992803359056244j) + assert zeta(0.5-1000000j, derivative=1).ae(11.636804066002521459 + 17.127254072212996004j) + # Additional sanity tests using fp arithmetic. + # Some more high-precision tests are found in the docstrings + def ae(x, y, tol=1e-6): + return abs(x-y) < tol*abs(y) + assert ae(fp.zeta(0.5-100000j), 1.0730320148577531321 - 5.7808485443635039843j) + assert ae(fp.zeta(0.75-100000j), 1.837852337251873704 - 1.9988492668661145358j) + assert ae(fp.zeta(0.5+1e6j), 0.076089069738227100006 + 2.8051021010192989554j) + assert ae(fp.zeta(0.5+1e6j, derivative=1), 11.636804066002521459 - 17.127254072212996004j) + assert ae(fp.zeta(1+1e6j), 0.94738726251047891048 + 0.59421999312091832833j) + assert ae(fp.zeta(1+1e6j, derivative=1), -0.10423479366985452134 - 0.74728992803359056244j) + assert ae(fp.zeta(0.5+100000j, derivative=1), 10.766962036817482375 - 30.92705282105996714j) + assert ae(fp.zeta(0.5+100000j, derivative=2), -119.40515625740538429 + 217.14780631141830251j) + assert ae(fp.zeta(0.5+100000j, derivative=3), 1129.7550282628460881 - 1685.4736895169690346j) + assert ae(fp.zeta(0.5+100000j, derivative=4), -10407.160819314958615 + 13777.786698628045085j) + assert ae(fp.zeta(0.75+100000j, derivative=1), -0.41742276699594321475 - 6.4453816275049955949j) + assert ae(fp.zeta(0.75+100000j, derivative=2), -9.214314279161977266 + 35.07290795337967899j) + assert ae(fp.zeta(0.75+100000j, derivative=3), 110.61331857820103469 - 236.87847130518129926j) + assert ae(fp.zeta(0.75+100000j, derivative=4), -1054.334275898559401 + 1769.9177890161596383j) + +def test_siegelz(): + mp.dps = 15 + assert siegelz(100000).ae(5.87959246868176504171) + assert siegelz(100000, derivative=2).ae(-54.1172711010126452832) + assert siegelz(100000, derivative=3).ae(-278.930831343966552538) + assert siegelz(100000+j,derivative=1).ae(678.214511857070283307-379.742160779916375413j) + + + +def test_zeta_near_1(): + # Test for a former bug in mpf_zeta and mpc_zeta + mp.dps = 15 + s1 = fadd(1, '1e-10', exact=True) + s2 = fadd(1, '-1e-10', exact=True) + s3 = fadd(1, '1e-10j', exact=True) + assert zeta(s1).ae(1.000000000057721566490881444e10) + assert zeta(s2).ae(-9.99999999942278433510574872e9) + z = zeta(s3) + assert z.real.ae(0.57721566490153286060) + assert z.imag.ae(-9.9999999999999999999927184e9) + mp.dps = 30 + s1 = fadd(1, '1e-50', exact=True) + s2 = fadd(1, '-1e-50', exact=True) + s3 = fadd(1, '1e-50j', exact=True) + assert zeta(s1).ae('1e50') + assert zeta(s2).ae('-1e50') + z = zeta(s3) + assert z.real.ae('0.57721566490153286060651209008240243104215933593992') + assert z.imag.ae('-1e50') diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_hp.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_hp.py new file mode 100644 index 0000000000000000000000000000000000000000..9eba0af798f64ac3f8d464e2d3bf231567a48c9b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_hp.py @@ -0,0 +1,291 @@ +""" +Check that the output from irrational functions is accurate for +high-precision input, from 5 to 200 digits. The reference values were +verified with Mathematica. +""" + +import time +from mpmath import * + +precs = [5, 15, 28, 35, 57, 80, 100, 150, 200] + +# sqrt(3) + pi/2 +a = \ +"3.302847134363773912758768033145623809041389953497933538543279275605"\ +"841220051904536395163599428307109666700184672047856353516867399774243594"\ +"67433521615861420725323528325327484262075464241255915238845599752675" + +# e + 1/euler**2 +b = \ +"5.719681166601007617111261398629939965860873957353320734275716220045750"\ +"31474116300529519620938123730851145473473708966080207482581266469342214"\ +"824842256999042984813905047895479210702109260221361437411947323431" + +# sqrt(a) +sqrt_a = \ +"1.817373691447021556327498239690365674922395036495564333152483422755"\ +"144321726165582817927383239308173567921345318453306994746434073691275094"\ +"484777905906961689902608644112196725896908619756404253109722911487" + +# sqrt(a+b*i).real +sqrt_abi_real = \ +"2.225720098415113027729407777066107959851146508557282707197601407276"\ +"89160998185797504198062911768240808839104987021515555650875977724230130"\ +"3584116233925658621288393930286871862273400475179312570274423840384" + +# sqrt(a+b*i).imag +sqrt_abi_imag = \ +"1.2849057639084690902371581529110949983261182430040898147672052833653668"\ +"0629534491275114877090834296831373498336559849050755848611854282001250"\ +"1924311019152914021365263161630765255610885489295778894976075186" + +# log(a) +log_a = \ +"1.194784864491089550288313512105715261520511949410072046160598707069"\ +"4336653155025770546309137440687056366757650909754708302115204338077595203"\ +"83005773986664564927027147084436553262269459110211221152925732612" + +# log(a+b*i).real +log_abi_real = \ +"1.8877985921697018111624077550443297276844736840853590212962006811663"\ +"04949387789489704203167470111267581371396245317618589339274243008242708"\ +"014251531496104028712866224020066439049377679709216784954509456421" + +# log(a+b*i).imag +log_abi_imag = \ +"1.0471204952840802663567714297078763189256357109769672185219334169734948"\ +"4265809854092437285294686651806426649541504240470168212723133326542181"\ +"8300136462287639956713914482701017346851009323172531601894918640" + +# exp(a) +exp_a = \ +"27.18994224087168661137253262213293847994194869430518354305430976149"\ +"382792035050358791398632888885200049857986258414049540376323785711941636"\ +"100358982497583832083513086941635049329804685212200507288797531143" + +# exp(a+b*i).real +exp_abi_real = \ +"22.98606617170543596386921087657586890620262522816912505151109385026"\ +"40160179326569526152851983847133513990281518417211964710397233157168852"\ +"4963130831190142571659948419307628119985383887599493378056639916701" + +# exp(a+b*i).imag +exp_abi_imag = \ +"-14.523557450291489727214750571590272774669907424478129280902375851196283"\ +"3377162379031724734050088565710975758824441845278120105728824497308303"\ +"6065619788140201636218705414429933685889542661364184694108251449" + +# a**b +pow_a_b = \ +"928.7025342285568142947391505837660251004990092821305668257284426997"\ +"361966028275685583421197860603126498884545336686124793155581311527995550"\ +"580229264427202446131740932666832138634013168125809402143796691154" + +# (a**(a+b*i)).real +pow_a_abi_real = \ +"44.09156071394489511956058111704382592976814280267142206420038656267"\ +"67707916510652790502399193109819563864568986234654864462095231138500505"\ +"8197456514795059492120303477512711977915544927440682508821426093455" + +# (a**(a+b*i)).imag +pow_a_abi_imag = \ +"27.069371511573224750478105146737852141664955461266218367212527612279886"\ +"9322304536553254659049205414427707675802193810711302947536332040474573"\ +"8166261217563960235014674118610092944307893857862518964990092301" + +# ((a+b*i)**(a+b*i)).real +pow_abi_abi_real = \ +"-0.15171310677859590091001057734676423076527145052787388589334350524"\ +"8084195882019497779202452975350579073716811284169068082670778986235179"\ +"0813026562962084477640470612184016755250592698408112493759742219150452"\ + +# ((a+b*i)**(a+b*i)).imag +pow_abi_abi_imag = \ +"1.2697592504953448936553147870155987153192995316950583150964099070426"\ +"4736837932577176947632535475040521749162383347758827307504526525647759"\ +"97547638617201824468382194146854367480471892602963428122896045019902" + +# sin(a) +sin_a = \ +"-0.16055653857469062740274792907968048154164433772938156243509084009"\ +"38437090841460493108570147191289893388608611542655654723437248152535114"\ +"528368009465836614227575701220612124204622383149391870684288862269631" + +# sin(1000*a) +sin_1000a = \ +"-0.85897040577443833776358106803777589664322997794126153477060795801"\ +"09151695416961724733492511852267067419573754315098042850381158563024337"\ +"216458577140500488715469780315833217177634490142748614625281171216863" + +# sin(a+b*i) +sin_abi_real = \ +"-24.4696999681556977743346798696005278716053366404081910969773939630"\ +"7149215135459794473448465734589287491880563183624997435193637389884206"\ +"02151395451271809790360963144464736839412254746645151672423256977064" + +sin_abi_imag = \ +"-150.42505378241784671801405965872972765595073690984080160750785565810981"\ +"8314482499135443827055399655645954830931316357243750839088113122816583"\ +"7169201254329464271121058839499197583056427233866320456505060735" + +# cos +cos_a = \ +"-0.98702664499035378399332439243967038895709261414476495730788864004"\ +"05406821549361039745258003422386169330787395654908532996287293003581554"\ +"257037193284199198069707141161341820684198547572456183525659969145501" + +cos_1000a = \ +"-0.51202523570982001856195696460663971099692261342827540426136215533"\ +"52686662667660613179619804463250686852463876088694806607652218586060613"\ +"951310588158830695735537073667299449753951774916401887657320950496820" + +# tan +tan_a = \ +"0.162666873675188117341401059858835168007137819495998960250142156848"\ +"639654718809412181543343168174807985559916643549174530459883826451064966"\ +"7996119428949951351938178809444268785629011625179962457123195557310" + +tan_abi_real = \ +"6.822696615947538488826586186310162599974827139564433912601918442911"\ +"1026830824380070400102213741875804368044342309515353631134074491271890"\ +"467615882710035471686578162073677173148647065131872116479947620E-6" + +tan_abi_imag = \ +"0.9999795833048243692245661011298447587046967777739649018690797625964167"\ +"1446419978852235960862841608081413169601038230073129482874832053357571"\ +"62702259309150715669026865777947502665936317953101462202542168429" + + +def test_hp(): + for dps in precs: + mp.dps = dps + 8 + aa = mpf(a) + bb = mpf(b) + a1000 = 1000*mpf(a) + abi = mpc(aa, bb) + mp.dps = dps + assert (sqrt(3) + pi/2).ae(aa) + assert (e + 1/euler**2).ae(bb) + + assert sqrt(aa).ae(mpf(sqrt_a)) + assert sqrt(abi).ae(mpc(sqrt_abi_real, sqrt_abi_imag)) + + assert log(aa).ae(mpf(log_a)) + assert log(abi).ae(mpc(log_abi_real, log_abi_imag)) + + assert exp(aa).ae(mpf(exp_a)) + assert exp(abi).ae(mpc(exp_abi_real, exp_abi_imag)) + + assert (aa**bb).ae(mpf(pow_a_b)) + assert (aa**abi).ae(mpc(pow_a_abi_real, pow_a_abi_imag)) + assert (abi**abi).ae(mpc(pow_abi_abi_real, pow_abi_abi_imag)) + + assert sin(a).ae(mpf(sin_a)) + assert sin(a1000).ae(mpf(sin_1000a)) + assert sin(abi).ae(mpc(sin_abi_real, sin_abi_imag)) + + assert cos(a).ae(mpf(cos_a)) + assert cos(a1000).ae(mpf(cos_1000a)) + + assert tan(a).ae(mpf(tan_a)) + assert tan(abi).ae(mpc(tan_abi_real, tan_abi_imag)) + + # check that complex cancellation is avoided so that both + # real and imaginary parts have high relative accuracy. + # abs_eps should be 0, but has to be set to 1e-205 to pass the + # 200-digit case, probably due to slight inaccuracy in the + # precomputed input + assert (tan(abi).real).ae(mpf(tan_abi_real), abs_eps=1e-205) + assert (tan(abi).imag).ae(mpf(tan_abi_imag), abs_eps=1e-205) + mp.dps = 460 + assert str(log(3))[-20:] == '02166121184001409826' + mp.dps = 15 + +# Since str(a) can differ in the last digit from rounded a, and I want +# to compare the last digits of big numbers with the results in Mathematica, +# I made this hack to get the last 20 digits of rounded a + +def last_digits(a): + r = repr(a) + s = str(a) + #dps = mp.dps + #mp.dps += 3 + m = 10 + r = r.replace(s[:-m],'') + r = r.replace("mpf('",'').replace("')",'') + num0 = 0 + for c in r: + if c == '0': + num0 += 1 + else: + break + b = float(int(r))/10**(len(r) - m) + if b >= 10**m - 0.5: # pragma: no cover + raise NotImplementedError + n = int(round(b)) + sn = str(n) + s = s[:-m] + '0'*num0 + sn + return s[-20:] + +# values checked with Mathematica +def test_log_hp(): + mp.dps = 2000 + a = mpf(10)**15000/3 + r = log(a) + res = last_digits(r) + # Mathematica N[Log[10^15000/3], 2000] + # ...7443804441768333470331 + assert res == '43804441768333470331' + + # see issue 145 + r = log(mpf(3)/2) + # Mathematica N[Log[3/2], 2000] + # ...69653749808140753263288 + res = last_digits(r) + assert res == '53749808140753263288' + + mp.dps = 10000 + r = log(2) + res = last_digits(r) + # Mathematica N[Log[2], 10000] + # ...695615913401856601359655561 + assert res == '13401856601359655561' + r = log(mpf(10)**10/3) + res = last_digits(r) + # Mathematica N[Log[10^10/3], 10000] + # ...587087654020631943060007154 + assert res == '54020631943060007154', res + r = log(mpf(10)**100/3) + res = last_digits(r) + # Mathematica N[Log[10^100/3], 10000] + # ,,,59246336539088351652334666 + assert res == '36539088351652334666', res + mp.dps += 10 + a = 1 - mpf(1)/10**10 + mp.dps -= 10 + r = log(a) + res = last_digits(r) + # ...3310334360482956137216724048322957404 + # 372167240483229574038733026370 + # Mathematica N[Log[1 - 10^-10]*10^10, 10000] + # ...60482956137216724048322957404 + assert res == '37216724048322957404', res + mp.dps = 10000 + mp.dps += 100 + a = 1 + mpf(1)/10**100 + mp.dps -= 100 + + r = log(a) + res = last_digits(+r) + # Mathematica N[Log[1 + 10^-100]*10^10, 10030] + # ...3994733877377412241546890854692521568292338268273 10^-91 + assert res == '39947338773774122415', res + + mp.dps = 15 + +def test_exp_hp(): + mp.dps = 4000 + r = exp(mpf(1)/10) + # IntegerPart[N[Exp[1/10] * 10^4000, 4000]] + # ...92167105162069688129 + assert int(r * 10**mp.dps) % 10**20 == 92167105162069688129 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_identify.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_identify.py new file mode 100644 index 0000000000000000000000000000000000000000..f75ab0bc4f04ecb614011e7f4599989465cab785 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_identify.py @@ -0,0 +1,19 @@ +from mpmath import * + +def test_pslq(): + mp.dps = 15 + assert pslq([3*pi+4*e/7, pi, e, log(2)]) == [7, -21, -4, 0] + assert pslq([4.9999999999999991, 1]) == [1, -5] + assert pslq([2,1]) == [1, -2] + +def test_identify(): + mp.dps = 20 + assert identify(zeta(4), ['log(2)', 'pi**4']) == '((1/90)*pi**4)' + mp.dps = 15 + assert identify(exp(5)) == 'exp(5)' + assert identify(exp(4)) == 'exp(4)' + assert identify(log(5)) == 'log(5)' + assert identify(exp(3*pi), ['pi']) == 'exp((3*pi))' + assert identify(3, full=True) == ['3', '3', '1/(1/3)', 'sqrt(9)', + '1/sqrt((1/9))', '(sqrt(12)/2)**2', '1/(sqrt(12)/6)**2'] + assert identify(pi+1, {'a':+pi}) == '(1 + 1*a)' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_interval.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_interval.py new file mode 100644 index 0000000000000000000000000000000000000000..251fd8b7ddb00074e8ae27cce4a01d8f4f8fe151 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_interval.py @@ -0,0 +1,453 @@ +from mpmath import * + +def test_interval_identity(): + iv.dps = 15 + assert mpi(2) == mpi(2, 2) + assert mpi(2) != mpi(-2, 2) + assert not (mpi(2) != mpi(2, 2)) + assert mpi(-1, 1) == mpi(-1, 1) + assert str(mpi('0.1')) == "[0.099999999999999991673, 0.10000000000000000555]" + assert repr(mpi('0.1')) == "mpi('0.099999999999999992', '0.10000000000000001')" + u = mpi(-1, 3) + assert -1 in u + assert 2 in u + assert 3 in u + assert -1.1 not in u + assert 3.1 not in u + assert mpi(-1, 3) in u + assert mpi(0, 1) in u + assert mpi(-1.1, 2) not in u + assert mpi(2.5, 3.1) not in u + w = mpi(-inf, inf) + assert mpi(-5, 5) in w + assert mpi(2, inf) in w + assert mpi(0, 2) in mpi(0, 10) + assert not (3 in mpi(-inf, 0)) + +def test_interval_hash(): + assert hash(mpi(3)) == hash(3) + assert hash(mpi(3.25)) == hash(3.25) + assert hash(mpi(3,4)) == hash(mpi(3,4)) + assert hash(iv.mpc(3)) == hash(3) + assert hash(iv.mpc(3,4)) == hash(3+4j) + assert hash(iv.mpc((1,3),(2,4))) == hash(iv.mpc((1,3),(2,4))) + +def test_interval_arithmetic(): + iv.dps = 15 + assert mpi(2) + mpi(3,4) == mpi(5,6) + assert mpi(1, 2)**2 == mpi(1, 4) + assert mpi(1) + mpi(0, 1e-50) == mpi(1, mpf('1.0000000000000002')) + x = 1 / (1 / mpi(3)) + assert x.a < 3 < x.b + x = mpi(2) ** mpi(0.5) + iv.dps += 5 + sq = iv.sqrt(2) + iv.dps -= 5 + assert x.a < sq < x.b + assert mpi(1) / mpi(1, inf) + assert mpi(2, 3) / inf == mpi(0, 0) + assert mpi(0) / inf == 0 + assert mpi(0) / 0 == mpi(-inf, inf) + assert mpi(inf) / 0 == mpi(-inf, inf) + assert mpi(0) * inf == mpi(-inf, inf) + assert 1 / mpi(2, inf) == mpi(0, 0.5) + assert str((mpi(50, 50) * mpi(-10, -10)) / 3) == \ + '[-166.66666666666668561, -166.66666666666665719]' + assert mpi(0, 4) ** 3 == mpi(0, 64) + assert mpi(2,4).mid == 3 + iv.dps = 30 + a = mpi(iv.pi) + iv.dps = 15 + b = +a + assert b.a < a.a + assert b.b > a.b + a = mpi(iv.pi) + assert a == +a + assert abs(mpi(-1,2)) == mpi(0,2) + assert abs(mpi(0.5,2)) == mpi(0.5,2) + assert abs(mpi(-3,2)) == mpi(0,3) + assert abs(mpi(-3,-0.5)) == mpi(0.5,3) + assert mpi(0) * mpi(2,3) == mpi(0) + assert mpi(2,3) * mpi(0) == mpi(0) + assert mpi(1,3).delta == 2 + assert mpi(1,2) - mpi(3,4) == mpi(-3,-1) + assert mpi(-inf,0) - mpi(0,inf) == mpi(-inf,0) + assert mpi(-inf,0) - mpi(-inf,inf) == mpi(-inf,inf) + assert mpi(0,inf) - mpi(-inf,1) == mpi(-1,inf) + +def test_interval_mul(): + assert mpi(-1, 0) * inf == mpi(-inf, 0) + assert mpi(-1, 0) * -inf == mpi(0, inf) + assert mpi(0, 1) * inf == mpi(0, inf) + assert mpi(0, 1) * mpi(0, inf) == mpi(0, inf) + assert mpi(-1, 1) * inf == mpi(-inf, inf) + assert mpi(-1, 1) * mpi(0, inf) == mpi(-inf, inf) + assert mpi(-1, 1) * mpi(-inf, inf) == mpi(-inf, inf) + assert mpi(-inf, 0) * mpi(0, 1) == mpi(-inf, 0) + assert mpi(-inf, 0) * mpi(0, 0) * mpi(-inf, 0) + assert mpi(-inf, 0) * mpi(-inf, inf) == mpi(-inf, inf) + assert mpi(-5,0)*mpi(-32,28) == mpi(-140,160) + assert mpi(2,3) * mpi(-1,2) == mpi(-3,6) + # Should be undefined? + assert mpi(inf, inf) * 0 == mpi(-inf, inf) + assert mpi(-inf, -inf) * 0 == mpi(-inf, inf) + assert mpi(0) * mpi(-inf,2) == mpi(-inf,inf) + assert mpi(0) * mpi(-2,inf) == mpi(-inf,inf) + assert mpi(-2,inf) * mpi(0) == mpi(-inf,inf) + assert mpi(-inf,2) * mpi(0) == mpi(-inf,inf) + +def test_interval_pow(): + assert mpi(3)**2 == mpi(9, 9) + assert mpi(-3)**2 == mpi(9, 9) + assert mpi(-3, 1)**2 == mpi(0, 9) + assert mpi(-3, -1)**2 == mpi(1, 9) + assert mpi(-3, -1)**3 == mpi(-27, -1) + assert mpi(-3, 1)**3 == mpi(-27, 1) + assert mpi(-2, 3)**2 == mpi(0, 9) + assert mpi(-3, 2)**2 == mpi(0, 9) + assert mpi(4) ** -1 == mpi(0.25, 0.25) + assert mpi(-4) ** -1 == mpi(-0.25, -0.25) + assert mpi(4) ** -2 == mpi(0.0625, 0.0625) + assert mpi(-4) ** -2 == mpi(0.0625, 0.0625) + assert mpi(0, 1) ** inf == mpi(0, 1) + assert mpi(0, 1) ** -inf == mpi(1, inf) + assert mpi(0, inf) ** inf == mpi(0, inf) + assert mpi(0, inf) ** -inf == mpi(0, inf) + assert mpi(1, inf) ** inf == mpi(1, inf) + assert mpi(1, inf) ** -inf == mpi(0, 1) + assert mpi(2, 3) ** 1 == mpi(2, 3) + assert mpi(2, 3) ** 0 == 1 + assert mpi(1,3) ** mpi(2) == mpi(1,9) + +def test_interval_sqrt(): + assert mpi(4) ** 0.5 == mpi(2) + +def test_interval_div(): + assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(0, 1) / mpi(0, 1) == mpi(0, inf) + assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(2, 2) == mpi(inf, inf) + assert mpi(0, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(0, inf) / mpi(2, 2) == mpi(0, inf) + assert mpi(2, inf) / mpi(2, 2) == mpi(1, inf) + assert mpi(2, inf) / mpi(2, inf) == mpi(0, inf) + assert mpi(-4, 8) / mpi(1, inf) == mpi(-4, 8) + assert mpi(-4, 8) / mpi(0.5, inf) == mpi(-8, 16) + assert mpi(-inf, 8) / mpi(0.5, inf) == mpi(-inf, 16) + assert mpi(-inf, inf) / mpi(0.5, inf) == mpi(-inf, inf) + assert mpi(8, inf) / mpi(0.5, inf) == mpi(0, inf) + assert mpi(-8, inf) / mpi(0.5, inf) == mpi(-16, inf) + assert mpi(-4, 8) / mpi(inf, inf) == mpi(0, 0) + assert mpi(0, 8) / mpi(inf, inf) == mpi(0, 0) + assert mpi(0, 0) / mpi(inf, inf) == mpi(0, 0) + assert mpi(-inf, 0) / mpi(inf, inf) == mpi(-inf, 0) + assert mpi(-inf, 8) / mpi(inf, inf) == mpi(-inf, 0) + assert mpi(-inf, inf) / mpi(inf, inf) == mpi(-inf, inf) + assert mpi(-8, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(0, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(8, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf) + assert mpi(-1, 2) / mpi(0, 1) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(0, 1) == mpi(0.0, +inf) + assert mpi(-1, 0) / mpi(0, 1) == mpi(-inf, 0.0) + assert mpi(-0.5, -0.25) / mpi(0, 1) == mpi(-inf, -0.25) + assert mpi(0.5, 1) / mpi(0, 1) == mpi(0.5, +inf) + assert mpi(0.5, 4) / mpi(0, 1) == mpi(0.5, +inf) + assert mpi(-1, -0.5) / mpi(0, 1) == mpi(-inf, -0.5) + assert mpi(-4, -0.5) / mpi(0, 1) == mpi(-inf, -0.5) + assert mpi(-1, 2) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, 0) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-0.5, -0.25) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0.5, 1) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(0.5, 4) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-4, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf) + assert mpi(-1, 2) / mpi(-1, 0) == mpi(-inf, +inf) + assert mpi(0, 1) / mpi(-1, 0) == mpi(-inf, 0.0) + assert mpi(-1, 0) / mpi(-1, 0) == mpi(0.0, +inf) + assert mpi(-0.5, -0.25) / mpi(-1, 0) == mpi(0.25, +inf) + assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(0.5, 4) / mpi(-1, 0) == mpi(-inf, -0.5) + assert mpi(-1, -0.5) / mpi(-1, 0) == mpi(0.5, +inf) + assert mpi(-4, -0.5) / mpi(-1, 0) == mpi(0.5, +inf) + assert mpi(-1, 2) / mpi(0.5, 1) == mpi(-2.0, 4.0) + assert mpi(0, 1) / mpi(0.5, 1) == mpi(0.0, 2.0) + assert mpi(-1, 0) / mpi(0.5, 1) == mpi(-2.0, 0.0) + assert mpi(-0.5, -0.25) / mpi(0.5, 1) == mpi(-1.0, -0.25) + assert mpi(0.5, 1) / mpi(0.5, 1) == mpi(0.5, 2.0) + assert mpi(0.5, 4) / mpi(0.5, 1) == mpi(0.5, 8.0) + assert mpi(-1, -0.5) / mpi(0.5, 1) == mpi(-2.0, -0.5) + assert mpi(-4, -0.5) / mpi(0.5, 1) == mpi(-8.0, -0.5) + assert mpi(-1, 2) / mpi(-2, -0.5) == mpi(-4.0, 2.0) + assert mpi(0, 1) / mpi(-2, -0.5) == mpi(-2.0, 0.0) + assert mpi(-1, 0) / mpi(-2, -0.5) == mpi(0.0, 2.0) + assert mpi(-0.5, -0.25) / mpi(-2, -0.5) == mpi(0.125, 1.0) + assert mpi(0.5, 1) / mpi(-2, -0.5) == mpi(-2.0, -0.25) + assert mpi(0.5, 4) / mpi(-2, -0.5) == mpi(-8.0, -0.25) + assert mpi(-1, -0.5) / mpi(-2, -0.5) == mpi(0.25, 2.0) + assert mpi(-4, -0.5) / mpi(-2, -0.5) == mpi(0.25, 8.0) + # Should be undefined? + assert mpi(0, 0) / mpi(0, 0) == mpi(-inf, inf) + assert mpi(0, 0) / mpi(0, 1) == mpi(-inf, inf) + +def test_interval_cos_sin(): + iv.dps = 15 + cos = iv.cos + sin = iv.sin + tan = iv.tan + pi = iv.pi + # Around 0 + assert cos(mpi(0)) == 1 + assert sin(mpi(0)) == 0 + assert cos(mpi(0,1)) == mpi(0.54030230586813965399, 1.0) + assert sin(mpi(0,1)) == mpi(0, 0.8414709848078966159) + assert cos(mpi(1,2)) == mpi(-0.4161468365471424069, 0.54030230586813976501) + assert sin(mpi(1,2)) == mpi(0.84147098480789650488, 1.0) + assert sin(mpi(1,2.5)) == mpi(0.59847214410395643824, 1.0) + assert cos(mpi(-1, 1)) == mpi(0.54030230586813965399, 1.0) + assert cos(mpi(-1, 0.5)) == mpi(0.54030230586813965399, 1.0) + assert cos(mpi(-1, 1.5)) == mpi(0.070737201667702906405, 1.0) + assert sin(mpi(-1,1)) == mpi(-0.8414709848078966159, 0.8414709848078966159) + assert sin(mpi(-1,0.5)) == mpi(-0.8414709848078966159, 0.47942553860420300538) + assert mpi(-0.8414709848078966159, 1.00000000000000002e-100) in sin(mpi(-1,1e-100)) + assert mpi(-2.00000000000000004e-100, 1.00000000000000002e-100) in sin(mpi(-2e-100,1e-100)) + # Same interval + assert cos(mpi(2, 2.5)) + assert cos(mpi(3.5, 4)) == mpi(-0.93645668729079634129, -0.65364362086361182946) + assert cos(mpi(5, 5.5)) == mpi(0.28366218546322624627, 0.70866977429126010168) + assert mpi(0.59847214410395654927, 0.90929742682568170942) in sin(mpi(2, 2.5)) + assert sin(mpi(3.5, 4)) == mpi(-0.75680249530792831347, -0.35078322768961983646) + assert sin(mpi(5, 5.5)) == mpi(-0.95892427466313856499, -0.70554032557039181306) + # Higher roots + iv.dps = 55 + w = 4*10**50 + mpi(0.5) + for p in [15, 40, 80]: + iv.dps = p + assert 0 in sin(4*mpi(pi)) + assert 0 in sin(4*10**50*mpi(pi)) + assert 0 in cos((4+0.5)*mpi(pi)) + assert 0 in cos(w*mpi(pi)) + assert 1 in cos(4*mpi(pi)) + assert 1 in cos(4*10**50*mpi(pi)) + iv.dps = 15 + assert cos(mpi(2,inf)) == mpi(-1,1) + assert sin(mpi(2,inf)) == mpi(-1,1) + assert cos(mpi(-inf,2)) == mpi(-1,1) + assert sin(mpi(-inf,2)) == mpi(-1,1) + u = tan(mpi(0.5,1)) + assert mpf(u.a).ae(mp.tan(0.5)) + assert mpf(u.b).ae(mp.tan(1)) + v = iv.cot(mpi(0.5,1)) + assert mpf(v.a).ae(mp.cot(1)) + assert mpf(v.b).ae(mp.cot(0.5)) + # Sanity check of evaluation at n*pi and (n+1/2)*pi + for n in range(-5,7,2): + x = iv.cos(n*iv.pi) + assert -1 in x + assert x >= -1 + assert x != -1 + x = iv.sin((n+0.5)*iv.pi) + assert -1 in x + assert x >= -1 + assert x != -1 + for n in range(-6,8,2): + x = iv.cos(n*iv.pi) + assert 1 in x + assert x <= 1 + if n: + assert x != 1 + x = iv.sin((n+0.5)*iv.pi) + assert 1 in x + assert x <= 1 + assert x != 1 + for n in range(-6,7): + x = iv.cos((n+0.5)*iv.pi) + assert x.a < 0 < x.b + x = iv.sin(n*iv.pi) + if n: + assert x.a < 0 < x.b + +def test_interval_complex(): + # TODO: many more tests + iv.dps = 15 + mp.dps = 15 + assert iv.mpc(2,3) == 2+3j + assert iv.mpc(2,3) != 2+4j + assert iv.mpc(2,3) != 1+3j + assert 1+3j in iv.mpc([1,2],[3,4]) + assert 2+5j not in iv.mpc([1,2],[3,4]) + assert iv.mpc(1,2) + 1j == 1+3j + assert iv.mpc([1,2],[2,3]) + 2+3j == iv.mpc([3,4],[5,6]) + assert iv.mpc([2,4],[4,8]) / 2 == iv.mpc([1,2],[2,4]) + assert iv.mpc([1,2],[2,4]) * 2j == iv.mpc([-8,-4],[2,4]) + assert iv.mpc([2,4],[4,8]) / 2j == iv.mpc([2,4],[-2,-1]) + assert iv.exp(2+3j).ae(mp.exp(2+3j)) + assert iv.log(2+3j).ae(mp.log(2+3j)) + assert (iv.mpc(2,3) ** iv.mpc(0.5,2)).ae(mp.mpc(2,3) ** mp.mpc(0.5,2)) + assert 1j in (iv.mpf(-1) ** 0.5) + assert 1j in (iv.mpc(-1) ** 0.5) + assert abs(iv.mpc(0)) == 0 + assert abs(iv.mpc(inf)) == inf + assert abs(iv.mpc(3,4)) == 5 + assert abs(iv.mpc(4)) == 4 + assert abs(iv.mpc(0,4)) == 4 + assert abs(iv.mpc(0,[2,3])) == iv.mpf([2,3]) + assert abs(iv.mpc(0,[-3,2])) == iv.mpf([0,3]) + assert abs(iv.mpc([3,5],[4,12])) == iv.mpf([5,13]) + assert abs(iv.mpc([3,5],[-4,12])) == iv.mpf([3,13]) + assert iv.mpc(2,3) ** 0 == 1 + assert iv.mpc(2,3) ** 1 == (2+3j) + assert iv.mpc(2,3) ** 2 == (2+3j)**2 + assert iv.mpc(2,3) ** 3 == (2+3j)**3 + assert iv.mpc(2,3) ** 4 == (2+3j)**4 + assert iv.mpc(2,3) ** 5 == (2+3j)**5 + assert iv.mpc(2,2) ** (-1) == (2+2j) ** (-1) + assert iv.mpc(2,2) ** (-2) == (2+2j) ** (-2) + assert iv.cos(2).ae(mp.cos(2)) + assert iv.sin(2).ae(mp.sin(2)) + assert iv.cos(2+3j).ae(mp.cos(2+3j)) + assert iv.sin(2+3j).ae(mp.sin(2+3j)) + +def test_interval_complex_arg(): + mp.dps = 15 + iv.dps = 15 + assert iv.arg(3) == 0 + assert iv.arg(0) == 0 + assert iv.arg([0,3]) == 0 + assert iv.arg(-3).ae(pi) + assert iv.arg(2+3j).ae(iv.arg(2+3j)) + z = iv.mpc([-2,-1],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-1+4j)) + assert t.b.ae(mp.arg(-2+3j)) + z = iv.mpc([-2,1],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1+3j)) + assert t.b.ae(mp.arg(-2+3j)) + z = iv.mpc([1,2],[3,4]) + t = iv.arg(z) + assert t.a.ae(mp.arg(2+3j)) + assert t.b.ae(mp.arg(1+4j)) + z = iv.mpc([1,2],[-2,3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1-2j)) + assert t.b.ae(mp.arg(1+3j)) + z = iv.mpc([1,2],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(1-4j)) + assert t.b.ae(mp.arg(2-3j)) + z = iv.mpc([-1,2],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-1-3j)) + assert t.b.ae(mp.arg(2-3j)) + z = iv.mpc([-2,-1],[-4,-3]) + t = iv.arg(z) + assert t.a.ae(mp.arg(-2-3j)) + assert t.b.ae(mp.arg(-1-4j)) + z = iv.mpc([-2,-1],[-3,3]) + t = iv.arg(z) + assert t.a.ae(-mp.pi) + assert t.b.ae(mp.pi) + z = iv.mpc([-2,2],[-3,3]) + t = iv.arg(z) + assert t.a.ae(-mp.pi) + assert t.b.ae(mp.pi) + +def test_interval_ae(): + iv.dps = 15 + x = iv.mpf([1,2]) + assert x.ae(1) is None + assert x.ae(1.5) is None + assert x.ae(2) is None + assert x.ae(2.01) is False + assert x.ae(0.99) is False + x = iv.mpf(3.5) + assert x.ae(3.5) is True + assert x.ae(3.5+1e-15) is True + assert x.ae(3.5-1e-15) is True + assert x.ae(3.501) is False + assert x.ae(3.499) is False + assert x.ae(iv.mpf([3.5,3.501])) is None + assert x.ae(iv.mpf([3.5,4.5+1e-15])) is None + +def test_interval_nstr(): + iv.dps = n = 30 + x = mpi(1, 2) + # FIXME: error_dps should not be necessary + assert iv.nstr(x, n, mode='plusminus', error_dps=6) == '1.5 +- 0.5' + assert iv.nstr(x, n, mode='plusminus', use_spaces=False, error_dps=6) == '1.5+-0.5' + assert iv.nstr(x, n, mode='percent') == '1.5 (33.33%)' + assert iv.nstr(x, n, mode='brackets', use_spaces=False) == '[1.0,2.0]' + assert iv.nstr(x, n, mode='brackets' , brackets=('<', '>')) == '<1.0, 2.0>' + x = mpi('5.2582327113062393041', '5.2582327113062749951') + assert iv.nstr(x, n, mode='diff') == '5.2582327113062[393041, 749951]' + assert iv.nstr(iv.cos(mpi(1)), n, mode='diff', use_spaces=False) == '0.54030230586813971740093660744[2955,3053]' + assert iv.nstr(mpi('1e123', '1e129'), n, mode='diff') == '[1.0e+123, 1.0e+129]' + exp = iv.exp + assert iv.nstr(iv.exp(mpi('5000.1')), n, mode='diff') == '3.2797365856787867069110487[0926, 1191]e+2171' + iv.dps = 15 + +def test_mpi_from_str(): + iv.dps = 15 + assert iv.convert('1.5 +- 0.5') == mpi(mpf('1.0'), mpf('2.0')) + assert mpi(1, 2) in iv.convert('1.5 (33.33333333333333333333333333333%)') + assert iv.convert('[1, 2]') == mpi(1, 2) + assert iv.convert('1[2, 3]') == mpi(12, 13) + assert iv.convert('1.[23,46]e-8') == mpi('1.23e-8', '1.46e-8') + assert iv.convert('12[3.4,5.9]e4') == mpi('123.4e+4', '125.9e4') + +def test_interval_gamma(): + mp.dps = 15 + iv.dps = 15 + # TODO: need many more tests + assert iv.rgamma(0) == 0 + assert iv.fac(0) == 1 + assert iv.fac(1) == 1 + assert iv.fac(2) == 2 + assert iv.fac(3) == 6 + assert iv.gamma(0) == [-inf,inf] + assert iv.gamma(1) == 1 + assert iv.gamma(2) == 1 + assert iv.gamma(3) == 2 + assert -3.5449077018110320546 in iv.gamma(-0.5) + assert iv.loggamma(1) == 0 + assert iv.loggamma(2) == 0 + assert 0.69314718055994530942 in iv.loggamma(3) + # Test tight log-gamma endpoints based on monotonicity + xs = [iv.mpc([2,3],[1,4]), + iv.mpc([2,3],[-4,-1]), + iv.mpc([2,3],[-1,4]), + iv.mpc([2,3],[-4,1]), + iv.mpc([2,3],[-4,4]), + iv.mpc([-3,-2],[2,4]), + iv.mpc([-3,-2],[-4,-2])] + for x in xs: + ys = [mp.loggamma(mp.mpc(x.a,x.c)), + mp.loggamma(mp.mpc(x.b,x.c)), + mp.loggamma(mp.mpc(x.a,x.d)), + mp.loggamma(mp.mpc(x.b,x.d))] + if 0 in x.imag: + ys += [mp.loggamma(x.a), mp.loggamma(x.b)] + min_real = min([y.real for y in ys]) + max_real = max([y.real for y in ys]) + min_imag = min([y.imag for y in ys]) + max_imag = max([y.imag for y in ys]) + z = iv.loggamma(x) + assert z.a.ae(min_real) + assert z.b.ae(max_real) + assert z.c.ae(min_imag) + assert z.d.ae(max_imag) + +def test_interval_conversions(): + mp.dps = 15 + iv.dps = 15 + for a, b in ((-0.0, 0), (0.0, 0.5), (1.0, 1), \ + ('-inf', 20.5), ('-inf', float(sqrt(2)))): + r = mpi(a, b) + assert int(r.b) == int(b) + assert float(r.a) == float(a) + assert float(r.b) == float(b) + assert complex(r.a) == complex(a) + assert complex(r.b) == complex(b) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_levin.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_levin.py new file mode 100644 index 0000000000000000000000000000000000000000..b14855df4de1a45da27080dcd239267842a4ac7a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_levin.py @@ -0,0 +1,153 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from mpmath import mp +from mpmath import libmp + +xrange = libmp.backend.xrange + +# Attention: +# These tests run with 15-20 decimal digits precision. For higher precision the +# working precision must be raised. + +def test_levin_0(): + mp.dps = 17 + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "levin", variant = "u") + S, s, n = [], 0, 1 + while 1: + s += mp.one / (n * n) + n += 1 + S.append(s) + v, e = L.update_psum(S) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + err = abs(v - mp.pi ** 2 / 6) + assert err < eps + w = mp.nsum(lambda n: 1/(n * n), [1, mp.inf], method = "levin", levin_variant = "u") + err = abs(v - w) + assert err < eps + +def test_levin_1(): + mp.dps = 17 + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "levin", variant = "v") + A, n = [], 1 + while 1: + s = mp.mpf(n) ** (2 + 3j) + n += 1 + A.append(s) + v, e = L.update(A) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + err = abs(v - mp.zeta(-2-3j)) + assert err < eps + w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v") + err = abs(v - w) + assert err < eps + +def test_levin_2(): + # [2] A. Sidi - "Pratical Extrapolation Methods" p.373 + mp.dps = 17 + z=mp.mpf(10) + eps = mp.mpf(mp.eps) + with mp.extraprec(2 * mp.prec): + L = mp.levin(method = "sidi", variant = "t") + n = 0 + while 1: + s = (-1)**n * mp.fac(n) * z ** (-n) + v, e = L.step(s) + n += 1 + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(eps)) + exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf]) + # there is also a symbolic expression for the integral: + # exact = z * mp.exp(z) * mp.expint(1,z) + err = abs(v - exact) + assert err < eps + w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t") + assert err < eps + +def test_levin_3(): + mp.dps = 17 + z=mp.mpf(2) + eps = mp.mpf(mp.eps) + with mp.extraprec(7*mp.prec): # we need copious amount of precision to sum this highly divergent series + L = mp.levin(method = "levin", variant = "t") + n, s = 0, 0 + while 1: + s += (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)) + n += 1 + v, e = L.step_psum(s) + if e < eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.8 * mp.log(eps)) + exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi) + # there is also a symbolic expression for the integral: + # exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi)) + err = abs(v - exact) + assert err < eps + w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in xrange(1000)]) + err = abs(v - w) + assert err < eps + +def test_levin_nsum(): + mp.dps = 17 + + with mp.extraprec(mp.prec): + z = mp.mpf(10) ** (-10) + a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "l") - 1 / z + assert abs(a - mp.euler) < 1e-10 + + eps = mp.exp(0.8 * mp.log(mp.eps)) + + a = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "sidi") + assert abs(a - mp.log(2)) < eps + + z = 2 + 1j + f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n)) + v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in xrange(1000)]) + exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z) + assert abs(exact - v) < eps + +def test_cohen_alt_0(): + mp.dps = 17 + AC = mp.cohen_alt() + S, s, n = [], 0, 1 + while 1: + s += -((-1) ** n) * mp.one / (n * n) + n += 1 + S.append(s) + v, e = AC.update_psum(S) + if e < mp.eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + eps = mp.exp(0.9 * mp.log(mp.eps)) + err = abs(v - mp.pi ** 2 / 12) + assert err < eps + +def test_cohen_alt_1(): + mp.dps = 17 + A = [] + AC = mp.cohen_alt() + n = 1 + while 1: + A.append( mp.loggamma(1 + mp.one / (2 * n - 1))) + A.append(-mp.loggamma(1 + mp.one / (2 * n))) + n += 1 + v, e = AC.update(A) + if e < mp.eps: + break + if n > 1000: raise RuntimeError("iteration limit exceeded") + v = mp.exp(v) + err = abs(v - 1.06215090557106) + assert err < 1e-12 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_linalg.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_linalg.py new file mode 100644 index 0000000000000000000000000000000000000000..14256a79f8953d3e4ef8b296258560d48204f547 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_linalg.py @@ -0,0 +1,332 @@ +# TODO: don't use round + +from __future__ import division + +import pytest +from mpmath import * +xrange = libmp.backend.xrange + +# XXX: these shouldn't be visible(?) +LU_decomp = mp.LU_decomp +L_solve = mp.L_solve +U_solve = mp.U_solve +householder = mp.householder +improve_solution = mp.improve_solution + +A1 = matrix([[3, 1, 6], + [2, 1, 3], + [1, 1, 1]]) +b1 = [2, 7, 4] + +A2 = matrix([[ 2, -1, -1, 2], + [ 6, -2, 3, -1], + [-4, 2, 3, -2], + [ 2, 0, 4, -3]]) +b2 = [3, -3, -2, -1] + +A3 = matrix([[ 1, 0, -1, -1, 0], + [ 0, 1, 1, 0, -1], + [ 4, -5, 2, 0, 0], + [ 0, 0, -2, 9,-12], + [ 0, 5, 0, 0, 12]]) +b3 = [0, 0, 0, 0, 50] + +A4 = matrix([[10.235, -4.56, 0., -0.035, 5.67], + [-2.463, 1.27, 3.97, -8.63, 1.08], + [-6.58, 0.86, -0.257, 9.32, -43.6 ], + [ 9.83, 7.39, -17.25, 0.036, 24.86], + [-9.31, 34.9, 78.56, 1.07, 65.8 ]]) +b4 = [8.95, 20.54, 7.42, 5.60, 58.43] + +A5 = matrix([[ 1, 2, -4], + [-2, -3, 5], + [ 3, 5, -8]]) + +A6 = matrix([[ 1.377360, 2.481400, 5.359190], + [ 2.679280, -1.229560, 25.560210], + [-1.225280+1.e6, 9.910180, -35.049900-1.e6]]) +b6 = [23.500000, -15.760000, 2.340000] + +A7 = matrix([[1, -0.5], + [2, 1], + [-2, 6]]) +b7 = [3, 2, -4] + +A8 = matrix([[1, 2, 3], + [-1, 0, 1], + [-1, -2, -1], + [1, 0, -1]]) +b8 = [1, 2, 3, 4] + +A9 = matrix([[ 4, 2, -2], + [ 2, 5, -4], + [-2, -4, 5.5]]) +b9 = [10, 16, -15.5] + +A10 = matrix([[1.0 + 1.0j, 2.0, 2.0], + [4.0, 5.0, 6.0], + [7.0, 8.0, 9.0]]) +b10 = [1.0, 1.0 + 1.0j, 1.0] + + +def test_LU_decomp(): + A = A3.copy() + b = b3 + A, p = LU_decomp(A) + y = L_solve(A, b, p) + x = U_solve(A, y) + assert p == [2, 1, 2, 3] + assert [round(i, 14) for i in x] == [3.78953107960742, 2.9989094874591098, + -0.081788440567070006, 3.8713195201744801, 2.9171210468920399] + A = A4.copy() + b = b4 + A, p = LU_decomp(A) + y = L_solve(A, b, p) + x = U_solve(A, y) + assert p == [0, 3, 4, 3] + assert [round(i, 14) for i in x] == [2.6383625899619201, 2.6643834462368399, + 0.79208015947958998, -2.5088376454101899, -1.0567657691375001] + A = randmatrix(3) + bak = A.copy() + LU_decomp(A, overwrite=1) + assert A != bak + +def test_inverse(): + for A in [A1, A2, A5]: + inv = inverse(A) + assert mnorm(A*inv - eye(A.rows), 1) < 1.e-14 + +def test_householder(): + mp.dps = 15 + A, b = A8, b8 + H, p, x, r = householder(extend(A, b)) + assert H == matrix( + [[mpf('3.0'), mpf('-2.0'), mpf('-1.0'), 0], + [-1.0,mpf('3.333333333333333'),mpf('-2.9999999999999991'),mpf('2.0')], + [-1.0, mpf('-0.66666666666666674'),mpf('2.8142135623730948'), + mpf('-2.8284271247461898')], + [1.0, mpf('-1.3333333333333333'),mpf('-0.20000000000000018'), + mpf('4.2426406871192857')]]) + assert p == [-2, -2, mpf('-1.4142135623730949')] + assert round(norm(r, 2), 10) == 4.2426406870999998 + + y = [102.102, 58.344, 36.463, 24.310, 17.017, 12.376, 9.282, 7.140, 5.610, + 4.488, 3.6465, 3.003] + + def coeff(n): + # similiar to Hilbert matrix + A = [] + for i in range(1, 13): + A.append([1. / (i + j - 1) for j in range(1, n + 1)]) + return matrix(A) + + residuals = [] + refres = [] + for n in range(2, 7): + A = coeff(n) + H, p, x, r = householder(extend(A, y)) + x = matrix(x) + y = matrix(y) + residuals.append(norm(r, 2)) + refres.append(norm(residual(A, x, y), 2)) + assert [round(res, 10) for res in residuals] == [15.1733888877, + 0.82378073210000002, 0.302645887, 0.0260109244, + 0.00058653999999999998] + assert norm(matrix(residuals) - matrix(refres), inf) < 1.e-13 + + def hilbert_cmplx(n): + # Complexified Hilbert matrix + A = hilbert(2*n,n) + v = randmatrix(2*n, 2, min=-1, max=1) + v = v.apply(lambda x: exp(1J*pi()*x)) + A = diag(v[:,0])*A*diag(v[:n,1]) + return A + + residuals_cmplx = [] + refres_cmplx = [] + for n in range(2, 10): + A = hilbert_cmplx(n) + H, p, x, r = householder(A.copy()) + residuals_cmplx.append(norm(r, 2)) + refres_cmplx.append(norm(residual(A[:,:n-1], x, A[:,n-1]), 2)) + assert norm(matrix(residuals_cmplx) - matrix(refres_cmplx), inf) < 1.e-13 + +def test_factorization(): + A = randmatrix(5) + P, L, U = lu(A) + assert mnorm(P*A - L*U, 1) < 1.e-15 + +def test_solve(): + assert norm(residual(A6, lu_solve(A6, b6), b6), inf) < 1.e-10 + assert norm(residual(A7, lu_solve(A7, b7), b7), inf) < 1.5 + assert norm(residual(A8, lu_solve(A8, b8), b8), inf) <= 3 + 1.e-10 + assert norm(residual(A6, qr_solve(A6, b6)[0], b6), inf) < 1.e-10 + assert norm(residual(A7, qr_solve(A7, b7)[0], b7), inf) < 1.5 + assert norm(residual(A8, qr_solve(A8, b8)[0], b8), 2) <= 4.3 + assert norm(residual(A10, lu_solve(A10, b10), b10), 2) < 1.e-10 + assert norm(residual(A10, qr_solve(A10, b10)[0], b10), 2) < 1.e-10 + +def test_solve_overdet_complex(): + A = matrix([[1, 2j], [3, 4j], [5, 6]]) + b = matrix([1 + j, 2, -j]) + assert norm(residual(A, lu_solve(A, b), b)) < 1.0208 + +def test_singular(): + mp.dps = 15 + A = [[5.6, 1.2], [7./15, .1]] + B = repr(zeros(2)) + b = [1, 2] + for i in ['lu_solve(%s, %s)' % (A, b), 'lu_solve(%s, %s)' % (B, b), + 'qr_solve(%s, %s)' % (A, b), 'qr_solve(%s, %s)' % (B, b)]: + pytest.raises((ZeroDivisionError, ValueError), lambda: eval(i)) + +def test_cholesky(): + assert fp.cholesky(fp.matrix(A9)) == fp.matrix([[2, 0, 0], [1, 2, 0], [-1, -3/2, 3/2]]) + x = fp.cholesky_solve(A9, b9) + assert fp.norm(fp.residual(A9, x, b9), fp.inf) == 0 + +def test_det(): + assert det(A1) == 1 + assert round(det(A2), 14) == 8 + assert round(det(A3)) == 1834 + assert round(det(A4)) == 4443376 + assert det(A5) == 1 + assert round(det(A6)) == 78356463 + assert det(zeros(3)) == 0 + +def test_cond(): + mp.dps = 15 + A = matrix([[1.2969, 0.8648], [0.2161, 0.1441]]) + assert cond(A, lambda x: mnorm(x,1)) == mpf('327065209.73817754') + assert cond(A, lambda x: mnorm(x,inf)) == mpf('327065209.73817754') + assert cond(A, lambda x: mnorm(x,'F')) == mpf('249729266.80008656') + +@extradps(50) +def test_precision(): + A = randmatrix(10, 10) + assert mnorm(inverse(inverse(A)) - A, 1) < 1.e-45 + +def test_interval_matrix(): + mp.dps = 15 + iv.dps = 15 + a = iv.matrix([['0.1','0.3','1.0'],['7.1','5.5','4.8'],['3.2','4.4','5.6']]) + b = iv.matrix(['4','0.6','0.5']) + c = iv.lu_solve(a, b) + assert c[0].delta < 1e-13 + assert c[1].delta < 1e-13 + assert c[2].delta < 1e-13 + assert 5.25823271130625686059275 in c[0] + assert -13.155049396267837541163 in c[1] + assert 7.42069154774972557628979 in c[2] + +def test_LU_cache(): + A = randmatrix(3) + LU = LU_decomp(A) + assert A._LU == LU_decomp(A) + A[0,0] = -1000 + assert A._LU is None + +def test_improve_solution(): + A = randmatrix(5, min=1e-20, max=1e20) + b = randmatrix(5, 1, min=-1000, max=1000) + x1 = lu_solve(A, b) + randmatrix(5, 1, min=-1e-5, max=1.e-5) + x2 = improve_solution(A, x1, b) + assert norm(residual(A, x2, b), 2) < norm(residual(A, x1, b), 2) + +def test_exp_pade(): + for i in range(3): + dps = 15 + extra = 15 + mp.dps = dps + extra + dm = 0 + N = 3 + dg = range(1,N+1) + a = diag(dg) + expa = diag([exp(x) for x in dg]) + # choose a random matrix not close to be singular + # to avoid adding too much extra precision in computing + # m**-1 * M * m + while abs(dm) < 0.01: + m = randmatrix(N) + dm = det(m) + m = m/dm + a1 = m**-1 * a * m + e2 = m**-1 * expa * m + mp.dps = dps + e1 = expm(a1, method='pade') + mp.dps = dps + extra + d = e2 - e1 + #print d + mp.dps = dps + assert norm(d, inf).ae(0) + mp.dps = 15 + +def test_qr(): + mp.dps = 15 # used default value for dps + lowlimit = -9 # lower limit of matrix element value + uplimit = 9 # uppter limit of matrix element value + maxm = 4 # max matrix size + flg = False # toggle to create real vs complex matrix + zero = mpf('0.0') + + for k in xrange(0,10): + exdps = 0 + mode = 'full' + flg = bool(k % 2) + + # generate arbitrary matrix size (2 to maxm) + num1 = nint(maxm*rand()) + num2 = nint(maxm*rand()) + m = int(max(num1, num2)) + n = int(min(num1, num2)) + + # create matrix + A = mp.matrix(m,n) + + # populate matrix values with arbitrary integers + if flg: + flg = False + dtype = 'complex' + for j in xrange(0,n): + for i in xrange(0,m): + val = nint(lowlimit + (uplimit-lowlimit)*rand()) + val2 = nint(lowlimit + (uplimit-lowlimit)*rand()) + A[i,j] = mpc(val, val2) + else: + flg = True + dtype = 'real' + for j in xrange(0,n): + for i in xrange(0,m): + val = nint(lowlimit + (uplimit-lowlimit)*rand()) + A[i,j] = mpf(val) + + # perform A -> QR decomposition + Q, R = qr(A, mode, edps = exdps) + + #print('\n\n A = \n', nstr(A, 4)) + #print('\n Q = \n', nstr(Q, 4)) + #print('\n R = \n', nstr(R, 4)) + #print('\n Q*R = \n', nstr(Q*R, 4)) + + maxnorm = mpf('1.0E-11') + n1 = norm(A - Q * R) + #print '\n Norm of A - Q * R = ', n1 + assert n1 <= maxnorm + + if dtype == 'real': + n1 = norm(eye(m) - Q.T * Q) + #print ' Norm of I - Q.T * Q = ', n1 + assert n1 <= maxnorm + + n1 = norm(eye(m) - Q * Q.T) + #print ' Norm of I - Q * Q.T = ', n1 + assert n1 <= maxnorm + + if dtype == 'complex': + n1 = norm(eye(m) - Q.T * Q.conjugate()) + #print ' Norm of I - Q.T * Q.conjugate() = ', n1 + assert n1 <= maxnorm + + n1 = norm(eye(m) - Q.conjugate() * Q.T) + #print ' Norm of I - Q.conjugate() * Q.T = ', n1 + assert n1 <= maxnorm diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_matrices.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_matrices.py new file mode 100644 index 0000000000000000000000000000000000000000..1547b90664dba66a98a7f026a04a4ed1aa1ed3b4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_matrices.py @@ -0,0 +1,253 @@ +import pytest +import sys +from mpmath import * + +def test_matrix_basic(): + A1 = matrix(3) + for i in range(3): + A1[i,i] = 1 + assert A1 == eye(3) + assert A1 == matrix(A1) + A2 = matrix(3, 2) + assert not A2._matrix__data + A3 = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + assert list(A3) == list(range(1, 10)) + A3[1,1] = 0 + assert not (1, 1) in A3._matrix__data + A4 = matrix([[1, 2, 3], [4, 5, 6]]) + A5 = matrix([[6, -1], [3, 2], [0, -3]]) + assert A4 * A5 == matrix([[12, -6], [39, -12]]) + assert A1 * A3 == A3 * A1 == A3 + pytest.raises(ValueError, lambda: A2*A2) + l = [[10, 20, 30], [40, 0, 60], [70, 80, 90]] + A6 = matrix(l) + assert A6.tolist() == l + assert A6 == eval(repr(A6)) + A6 = fp.matrix(A6) + assert A6 == eval(repr(A6)) + assert A6*1j == eval(repr(A6*1j)) + assert A3 * 10 == 10 * A3 == A6 + assert A2.rows == 3 + assert A2.cols == 2 + A3.rows = 2 + A3.cols = 2 + assert len(A3._matrix__data) == 3 + assert A4 + A4 == 2*A4 + pytest.raises(ValueError, lambda: A4 + A2) + assert sum(A1 - A1) == 0 + A7 = matrix([[1, 2], [3, 4], [5, 6], [7, 8]]) + x = matrix([10, -10]) + assert A7*x == matrix([-10, -10, -10, -10]) + A8 = ones(5) + assert sum((A8 + 1) - (2 - zeros(5))) == 0 + assert (1 + ones(4)) / 2 - 1 == zeros(4) + assert eye(3)**10 == eye(3) + pytest.raises(ValueError, lambda: A7**2) + A9 = randmatrix(3) + A10 = matrix(A9) + A9[0,0] = -100 + assert A9 != A10 + assert nstr(A9) + +def test_matmul(): + """ + Test the PEP465 "@" matrix multiplication syntax. + To avoid syntax errors when importing this file in Python 3.5 and below, we have to use exec() - sorry for that. + """ + # TODO remove exec() wrapper as soon as we drop support for Python <= 3.5 + if sys.hexversion < 0x30500f0: + # we are on Python < 3.5 + pytest.skip("'@' (__matmul__) is only supported in Python 3.5 or newer") + A4 = matrix([[1, 2, 3], [4, 5, 6]]) + A5 = matrix([[6, -1], [3, 2], [0, -3]]) + exec("assert A4 @ A5 == A4 * A5") + +def test_matrix_slices(): + A = matrix([ [1, 2, 3], + [4, 5 ,6], + [7, 8 ,9]]) + V = matrix([1,2,3,4,5]) + + # Get slice + assert A[:,:] == A + assert A[:,1] == matrix([[2],[5],[8]]) + assert A[2,:] == matrix([[7, 8 ,9]]) + assert A[1:3,1:3] == matrix([[5,6],[8,9]]) + assert V[2:4] == matrix([3,4]) + pytest.raises(IndexError, lambda: A[:,1:6]) + + # Assign slice with matrix + A1 = matrix(3) + A1[:,:] = A + assert A1[:,:] == matrix([[1, 2, 3], + [4, 5 ,6], + [7, 8 ,9]]) + A1[0,:] = matrix([[10, 11, 12]]) + assert A1 == matrix([ [10, 11, 12], + [4, 5 ,6], + [7, 8 ,9]]) + A1[:,2] = matrix([[13], [14], [15]]) + assert A1 == matrix([ [10, 11, 13], + [4, 5 ,14], + [7, 8 ,15]]) + A1[:2,:2] = matrix([[16, 17], [18 , 19]]) + assert A1 == matrix([ [16, 17, 13], + [18, 19 ,14], + [7, 8 ,15]]) + V[1:3] = 10 + assert V == matrix([1,10,10,4,5]) + with pytest.raises(ValueError): + A1[2,:] = A[:,1] + + with pytest.raises(IndexError): + A1[2,1:20] = A[:,:] + + # Assign slice with scalar + A1[:,2] = 10 + assert A1 == matrix([ [16, 17, 10], + [18, 19 ,10], + [7, 8 ,10]]) + A1[:,:] = 40 + for x in A1: + assert x == 40 + + +def test_matrix_power(): + A = matrix([[1, 2], [3, 4]]) + assert A**2 == A*A + assert A**3 == A*A*A + assert A**-1 == inverse(A) + assert A**-2 == inverse(A*A) + +def test_matrix_transform(): + A = matrix([[1, 2], [3, 4], [5, 6]]) + assert A.T == A.transpose() == matrix([[1, 3, 5], [2, 4, 6]]) + swap_row(A, 1, 2) + assert A == matrix([[1, 2], [5, 6], [3, 4]]) + l = [1, 2] + swap_row(l, 0, 1) + assert l == [2, 1] + assert extend(eye(3), [1,2,3]) == matrix([[1,0,0,1],[0,1,0,2],[0,0,1,3]]) + +def test_matrix_conjugate(): + A = matrix([[1 + j, 0], [2, j]]) + assert A.conjugate() == matrix([[mpc(1, -1), 0], [2, mpc(0, -1)]]) + assert A.transpose_conj() == A.H == matrix([[mpc(1, -1), 2], + [0, mpc(0, -1)]]) + +def test_matrix_creation(): + assert diag([1, 2, 3]) == matrix([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) + A1 = ones(2, 3) + assert A1.rows == 2 and A1.cols == 3 + for a in A1: + assert a == 1 + A2 = zeros(3, 2) + assert A2.rows == 3 and A2.cols == 2 + for a in A2: + assert a == 0 + assert randmatrix(10) != randmatrix(10) + one = mpf(1) + assert hilbert(3) == matrix([[one, one/2, one/3], + [one/2, one/3, one/4], + [one/3, one/4, one/5]]) + +def test_norms(): + # matrix norms + A = matrix([[1, -2], [-3, -1], [2, 1]]) + assert mnorm(A,1) == 6 + assert mnorm(A,inf) == 4 + assert mnorm(A,'F') == sqrt(20) + # vector norms + assert norm(-3) == 3 + x = [1, -2, 7, -12] + assert norm(x, 1) == 22 + assert round(norm(x, 2), 10) == 14.0712472795 + assert round(norm(x, 10), 10) == 12.0054633727 + assert norm(x, inf) == 12 + +def test_vector(): + x = matrix([0, 1, 2, 3, 4]) + assert x == matrix([[0], [1], [2], [3], [4]]) + assert x[3] == 3 + assert len(x._matrix__data) == 4 + assert list(x) == list(range(5)) + x[0] = -10 + x[4] = 0 + assert x[0] == -10 + assert len(x) == len(x.T) == 5 + assert x.T*x == matrix([[114]]) + +def test_matrix_copy(): + A = ones(6) + B = A.copy() + C = +A + assert A == B + assert A == C + B[0,0] = 0 + assert A != B + C[0,0] = 42 + assert A != C + +def test_matrix_numpy(): + try: + import numpy + except ImportError: + return + l = [[1, 2], [3, 4], [5, 6]] + a = numpy.array(l) + assert matrix(l) == matrix(a) + +def test_interval_matrix_scalar_mult(): + """Multiplication of iv.matrix and any scalar type""" + a = mpi(-1, 1) + b = a + a * 2j + c = mpf(42) + d = c + c * 2j + e = 1.234 + f = fp.convert(e) + g = e + e * 3j + h = fp.convert(g) + M = iv.ones(1) + for x in [a, b, c, d, e, f, g, h]: + assert x * M == iv.matrix([x]) + assert M * x == iv.matrix([x]) + +@pytest.mark.xfail() +def test_interval_matrix_matrix_mult(): + """Multiplication of iv.matrix and other matrix types""" + A = ones(1) + B = fp.ones(1) + M = iv.ones(1) + for X in [A, B, M]: + assert X * M == iv.matrix(X) + assert X * M == X + assert M * X == iv.matrix(X) + assert M * X == X + +def test_matrix_conversion_to_iv(): + # Test that matrices with foreign datatypes are properly converted + for other_type_eye in [eye(3), fp.eye(3), iv.eye(3)]: + A = iv.matrix(other_type_eye) + B = iv.eye(3) + assert type(A[0,0]) == type(B[0,0]) + assert A.tolist() == B.tolist() + +def test_interval_matrix_mult_bug(): + # regression test for interval matrix multiplication: + # result must be nonzero-width and contain the exact result + x = convert('1.00000000000001') # note: this is implicitly rounded to some near mpf float value + A = matrix([[x]]) + B = iv.matrix(A) + C = iv.matrix([[x]]) + assert B == C + B = B * B + C = C * C + assert B == C + assert B[0, 0].delta > 1e-16 + assert B[0, 0].delta < 3e-16 + assert C[0, 0].delta > 1e-16 + assert C[0, 0].delta < 3e-16 + assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in B[0, 0] + assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in C[0, 0] + # the following caused an error before the bug was fixed + assert iv.matrix(mp.eye(2)) * (iv.ones(2) + mpi(1, 2)) == iv.matrix([[mpi(2, 3), mpi(2, 3)], [mpi(2, 3), mpi(2, 3)]]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_mpmath.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_mpmath.py new file mode 100644 index 0000000000000000000000000000000000000000..9f1fe36ae9b1b0feca4677eeb90396bfa7ed8f7a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_mpmath.py @@ -0,0 +1,7 @@ +from mpmath.libmp import * +from mpmath import * + +def test_newstyle_classes(): + for cls in [mp, fp, iv, mpf, mpc]: + for s in cls.__class__.__mro__: + assert isinstance(s, type) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_ode.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_ode.py new file mode 100644 index 0000000000000000000000000000000000000000..6b6dbffa79cfd4ca6dbf14f8591296ee48b16682 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_ode.py @@ -0,0 +1,73 @@ +#from mpmath.calculus import ODE_step_euler, ODE_step_rk4, odeint, arange +from mpmath import odefun, cos, sin, mpf, sinc, mp + +''' +solvers = [ODE_step_euler, ODE_step_rk4] + +def test_ode1(): + """ + Let's solve: + + x'' + w**2 * x = 0 + + i.e. x1 = x, x2 = x1': + + x1' = x2 + x2' = -x1 + """ + def derivs((x1, x2), t): + return x2, -x1 + + for solver in solvers: + t = arange(0, 3.1415926, 0.005) + sol = odeint(derivs, (0., 1.), t, solver) + x1 = [a[0] for a in sol] + x2 = [a[1] for a in sol] + # the result is x1 = sin(t), x2 = cos(t) + # let's just check the end points for t = pi + assert abs(x1[-1]) < 1e-2 + assert abs(x2[-1] - (-1)) < 1e-2 + +def test_ode2(): + """ + Let's solve: + + x' - x = 0 + + i.e. x = exp(x) + + """ + def derivs((x), t): + return x + + for solver in solvers: + t = arange(0, 1, 1e-3) + sol = odeint(derivs, (1.,), t, solver) + x = [a[0] for a in sol] + # the result is x = exp(t) + # let's just check the end point for t = 1, i.e. x = e + assert abs(x[-1] - 2.718281828) < 1e-2 +''' + +def test_odefun_rational(): + mp.dps = 15 + # A rational function + f = lambda t: 1/(1+mpf(t)**2) + g = odefun(lambda x, y: [-2*x*y[0]**2], 0, [f(0)]) + assert f(2).ae(g(2)[0]) + +def test_odefun_sinc_large(): + mp.dps = 15 + # Sinc function; test for large x + f = sinc + g = odefun(lambda x, y: [(cos(x)-y[0])/x], 1, [f(1)], tol=0.01, degree=5) + assert abs(f(100) - g(100)[0])/f(100) < 0.01 + +def test_odefun_harmonic(): + mp.dps = 15 + # Harmonic oscillator + f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0]) + for x in [0, 1, 2.5, 8, 3.7]: # we go back to 3.7 to check caching + c, s = f(x) + assert c.ae(cos(x)) + assert s.ae(sin(x)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_pickle.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_pickle.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d96e73a53603e0fa3f9525c5c0059725bdffb7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_pickle.py @@ -0,0 +1,27 @@ +import os +import tempfile +import pickle + +from mpmath import * + +def pickler(obj): + fn = tempfile.mktemp() + + f = open(fn, 'wb') + pickle.dump(obj, f) + f.close() + + f = open(fn, 'rb') + obj2 = pickle.load(f) + f.close() + os.remove(fn) + + return obj2 + +def test_pickle(): + + obj = mpf('0.5') + assert obj == pickler(obj) + + obj = mpc('0.5','0.2') + assert obj == pickler(obj) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_power.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_power.py new file mode 100644 index 0000000000000000000000000000000000000000..7a2447a62c36f9e02df79b9a40a8603f8a69b1d8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_power.py @@ -0,0 +1,156 @@ +from mpmath import * +from mpmath.libmp import * + +import random + +def test_fractional_pow(): + mp.dps = 15 + assert mpf(16) ** 2.5 == 1024 + assert mpf(64) ** 0.5 == 8 + assert mpf(64) ** -0.5 == 0.125 + assert mpf(16) ** -2.5 == 0.0009765625 + assert (mpf(10) ** 0.5).ae(3.1622776601683791) + assert (mpf(10) ** 2.5).ae(316.2277660168379) + assert (mpf(10) ** -0.5).ae(0.31622776601683794) + assert (mpf(10) ** -2.5).ae(0.0031622776601683794) + assert (mpf(10) ** 0.3).ae(1.9952623149688795) + assert (mpf(10) ** -0.3).ae(0.50118723362727224) + +def test_pow_integer_direction(): + """ + Test that inexact integer powers are rounded in the right + direction. + """ + random.seed(1234) + for prec in [10, 53, 200]: + for i in range(50): + a = random.randint(1<<(prec-1), 1< ab + + +def test_pow_epsilon_rounding(): + """ + Stress test directed rounding for powers with integer exponents. + Basically, we look at the following cases: + + >>> 1.0001 ** -5 # doctest: +SKIP + 0.99950014996500702 + >>> 0.9999 ** -5 # doctest: +SKIP + 1.000500150035007 + >>> (-1.0001) ** -5 # doctest: +SKIP + -0.99950014996500702 + >>> (-0.9999) ** -5 # doctest: +SKIP + -1.000500150035007 + + >>> 1.0001 ** -6 # doctest: +SKIP + 0.99940020994401269 + >>> 0.9999 ** -6 # doctest: +SKIP + 1.0006002100560125 + >>> (-1.0001) ** -6 # doctest: +SKIP + 0.99940020994401269 + >>> (-0.9999) ** -6 # doctest: +SKIP + 1.0006002100560125 + + etc. + + We run the tests with values a very small epsilon away from 1: + small enough that the result is indistinguishable from 1 when + rounded to nearest at the output precision. We check that the + result is not erroneously rounded to 1 in cases where the + rounding should be done strictly away from 1. + """ + + def powr(x, n, r): + return make_mpf(mpf_pow_int(x._mpf_, n, mp.prec, r)) + + for (inprec, outprec) in [(100, 20), (5000, 3000)]: + + mp.prec = inprec + + pos10001 = mpf(1) + mpf(2)**(-inprec+5) + pos09999 = mpf(1) - mpf(2)**(-inprec+5) + neg10001 = -pos10001 + neg09999 = -pos09999 + + mp.prec = outprec + r = round_up + assert powr(pos10001, 5, r) > 1 + assert powr(pos09999, 5, r) == 1 + assert powr(neg10001, 5, r) < -1 + assert powr(neg09999, 5, r) == -1 + assert powr(pos10001, 6, r) > 1 + assert powr(pos09999, 6, r) == 1 + assert powr(neg10001, 6, r) > 1 + assert powr(neg09999, 6, r) == 1 + + assert powr(pos10001, -5, r) == 1 + assert powr(pos09999, -5, r) > 1 + assert powr(neg10001, -5, r) == -1 + assert powr(neg09999, -5, r) < -1 + assert powr(pos10001, -6, r) == 1 + assert powr(pos09999, -6, r) > 1 + assert powr(neg10001, -6, r) == 1 + assert powr(neg09999, -6, r) > 1 + + r = round_down + assert powr(pos10001, 5, r) == 1 + assert powr(pos09999, 5, r) < 1 + assert powr(neg10001, 5, r) == -1 + assert powr(neg09999, 5, r) > -1 + assert powr(pos10001, 6, r) == 1 + assert powr(pos09999, 6, r) < 1 + assert powr(neg10001, 6, r) == 1 + assert powr(neg09999, 6, r) < 1 + + assert powr(pos10001, -5, r) < 1 + assert powr(pos09999, -5, r) == 1 + assert powr(neg10001, -5, r) > -1 + assert powr(neg09999, -5, r) == -1 + assert powr(pos10001, -6, r) < 1 + assert powr(pos09999, -6, r) == 1 + assert powr(neg10001, -6, r) < 1 + assert powr(neg09999, -6, r) == 1 + + r = round_ceiling + assert powr(pos10001, 5, r) > 1 + assert powr(pos09999, 5, r) == 1 + assert powr(neg10001, 5, r) == -1 + assert powr(neg09999, 5, r) > -1 + assert powr(pos10001, 6, r) > 1 + assert powr(pos09999, 6, r) == 1 + assert powr(neg10001, 6, r) > 1 + assert powr(neg09999, 6, r) == 1 + + assert powr(pos10001, -5, r) == 1 + assert powr(pos09999, -5, r) > 1 + assert powr(neg10001, -5, r) > -1 + assert powr(neg09999, -5, r) == -1 + assert powr(pos10001, -6, r) == 1 + assert powr(pos09999, -6, r) > 1 + assert powr(neg10001, -6, r) == 1 + assert powr(neg09999, -6, r) > 1 + + r = round_floor + assert powr(pos10001, 5, r) == 1 + assert powr(pos09999, 5, r) < 1 + assert powr(neg10001, 5, r) < -1 + assert powr(neg09999, 5, r) == -1 + assert powr(pos10001, 6, r) == 1 + assert powr(pos09999, 6, r) < 1 + assert powr(neg10001, 6, r) == 1 + assert powr(neg09999, 6, r) < 1 + + assert powr(pos10001, -5, r) < 1 + assert powr(pos09999, -5, r) == 1 + assert powr(neg10001, -5, r) == -1 + assert powr(neg09999, -5, r) < -1 + assert powr(pos10001, -6, r) < 1 + assert powr(pos09999, -6, r) == 1 + assert powr(neg10001, -6, r) < 1 + assert powr(neg09999, -6, r) == 1 + + mp.dps = 15 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_quad.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_quad.py new file mode 100644 index 0000000000000000000000000000000000000000..fc71c5f5ef9c0ecd876c988e7d033b321f065cdc --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_quad.py @@ -0,0 +1,95 @@ +import pytest +from mpmath import * + +def ae(a, b): + return abs(a-b) < 10**(-mp.dps+5) + +def test_basic_integrals(): + for prec in [15, 30, 100]: + mp.dps = prec + assert ae(quadts(lambda x: x**3 - 3*x**2, [-2, 4]), -12) + assert ae(quadgl(lambda x: x**3 - 3*x**2, [-2, 4]), -12) + assert ae(quadts(sin, [0, pi]), 2) + assert ae(quadts(sin, [0, 2*pi]), 0) + assert ae(quadts(exp, [-inf, -1]), 1/e) + assert ae(quadts(lambda x: exp(-x), [0, inf]), 1) + assert ae(quadts(lambda x: exp(-x*x), [-inf, inf]), sqrt(pi)) + assert ae(quadts(lambda x: 1/(1+x*x), [-1, 1]), pi/2) + assert ae(quadts(lambda x: 1/(1+x*x), [-inf, inf]), pi) + assert ae(quadts(lambda x: 2*sqrt(1-x*x), [-1, 1]), pi) + mp.dps = 15 + +def test_multiple_intervals(): + y,err = quad(lambda x: sign(x), [-0.5, 0.9, 1], maxdegree=2, error=True) + assert abs(y-0.5) < 2*err + +def test_quad_symmetry(): + assert quadts(sin, [-1, 1]) == 0 + assert quadgl(sin, [-1, 1]) == 0 + +def test_quad_infinite_mirror(): + # Check mirrored infinite interval + assert ae(quad(lambda x: exp(-x*x), [inf,-inf]), -sqrt(pi)) + assert ae(quad(lambda x: exp(x), [0,-inf]), -1) + +def test_quadgl_linear(): + assert quadgl(lambda x: x, [0, 1], maxdegree=1).ae(0.5) + +def test_complex_integration(): + assert quadts(lambda x: x, [0, 1+j]).ae(j) + +def test_quadosc(): + mp.dps = 15 + assert quadosc(lambda x: sin(x)/x, [0, inf], period=2*pi).ae(pi/2) + +# Double integrals +def test_double_trivial(): + assert ae(quadts(lambda x, y: x, [0, 1], [0, 1]), 0.5) + assert ae(quadts(lambda x, y: x, [-1, 1], [-1, 1]), 0.0) + +def test_double_1(): + assert ae(quadts(lambda x, y: cos(x+y/2), [-pi/2, pi/2], [0, pi]), 4) + +def test_double_2(): + assert ae(quadts(lambda x, y: (x-1)/((1-x*y)*log(x*y)), [0, 1], [0, 1]), euler) + +def test_double_3(): + assert ae(quadts(lambda x, y: 1/sqrt(1+x*x+y*y), [-1, 1], [-1, 1]), 4*log(2+sqrt(3))-2*pi/3) + +def test_double_4(): + assert ae(quadts(lambda x, y: 1/(1-x*x * y*y), [0, 1], [0, 1]), pi**2 / 8) + +def test_double_5(): + assert ae(quadts(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]), pi**2 / 6) + +def test_double_6(): + assert ae(quadts(lambda x, y: exp(-(x+y)), [0, inf], [0, inf]), 1) + +def test_double_7(): + assert ae(quadts(lambda x, y: exp(-x*x-y*y), [-inf, inf], [-inf, inf]), pi) + + +# Test integrals from "Experimentation in Mathematics" by Borwein, +# Bailey & Girgensohn +def test_expmath_integrals(): + for prec in [15, 30, 50]: + mp.dps = prec + assert ae(quadts(lambda x: x/sinh(x), [0, inf]), pi**2 / 4) + assert ae(quadts(lambda x: log(x)**2 / (1+x**2), [0, inf]), pi**3 / 8) + assert ae(quadts(lambda x: (1+x**2)/(1+x**4), [0, inf]), pi/sqrt(2)) + assert ae(quadts(lambda x: log(x)/cosh(x)**2, [0, inf]), log(pi)-2*log(2)-euler) + assert ae(quadts(lambda x: log(1+x**3)/(1-x+x**2), [0, inf]), 2*pi*log(3)/sqrt(3)) + assert ae(quadts(lambda x: log(x)**2 / (x**2+x+1), [0, 1]), 8*pi**3 / (81*sqrt(3))) + assert ae(quadts(lambda x: log(cos(x))**2, [0, pi/2]), pi/2 * (log(2)**2+pi**2/12)) + assert ae(quadts(lambda x: x**2 / sin(x)**2, [0, pi/2]), pi*log(2)) + assert ae(quadts(lambda x: x**2/sqrt(exp(x)-1), [0, inf]), 4*pi*(log(2)**2 + pi**2/12)) + assert ae(quadts(lambda x: x*exp(-x)*sqrt(1-exp(-2*x)), [0, inf]), pi*(1+2*log(2))/8) + mp.dps = 15 + +# Do not reach full accuracy +@pytest.mark.xfail +def test_expmath_fail(): + assert ae(quadts(lambda x: sqrt(tan(x)), [0, pi/2]), pi*sqrt(2)/2) + assert ae(quadts(lambda x: atan(x)/(x*sqrt(1-x**2)), [0, 1]), pi*log(1+sqrt(2))/2) + assert ae(quadts(lambda x: log(1+x**2)/x**2, [0, 1]), pi/2-log(2)) + assert ae(quadts(lambda x: x**2/((1+x**4)*sqrt(1-x**4)), [0, 1]), pi/8) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_rootfinding.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_rootfinding.py new file mode 100644 index 0000000000000000000000000000000000000000..7c3c06463682eb1fd60efeb75b809bbb932a241c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_rootfinding.py @@ -0,0 +1,91 @@ +import pytest +from mpmath import * +from mpmath.calculus.optimization import Secant, Muller, Bisection, Illinois, \ + Pegasus, Anderson, Ridder, ANewton, Newton, MNewton, MDNewton + +def test_findroot(): + # old tests, assuming secant + mp.dps = 15 + assert findroot(lambda x: 4*x-3, mpf(5)).ae(0.75) + assert findroot(sin, mpf(3)).ae(pi) + assert findroot(sin, (mpf(3), mpf(3.14))).ae(pi) + assert findroot(lambda x: x*x+1, mpc(2+2j)).ae(1j) + # test all solvers with 1 starting point + f = lambda x: cos(x) + for solver in [Newton, Secant, MNewton, Muller, ANewton]: + x = findroot(f, 2., solver=solver) + assert abs(f(x)) < eps + # test all solvers with interval of 2 points + for solver in [Secant, Muller, Bisection, Illinois, Pegasus, Anderson, + Ridder]: + x = findroot(f, (1., 2.), solver=solver) + assert abs(f(x)) < eps + # test types + f = lambda x: (x - 2)**2 + + assert isinstance(findroot(f, 1, tol=1e-10), mpf) + assert isinstance(iv.findroot(f, 1., tol=1e-10), iv.mpf) + assert isinstance(fp.findroot(f, 1, tol=1e-10), float) + assert isinstance(fp.findroot(f, 1+0j, tol=1e-10), complex) + + # issue 401 + with pytest.raises(ValueError): + with workprec(2): + findroot(lambda x: x**2 - 4456178*x + 60372201703370, + mpc(real='5.278e+13', imag='-5.278e+13')) + + # issue 192 + with pytest.raises(ValueError): + findroot(lambda x: -1, 0) + + # issue 387 + with pytest.raises(ValueError): + findroot(lambda p: (1 - p)**30 - 1, 0.9) + +def test_bisection(): + # issue 273 + assert findroot(lambda x: x**2-1,(0,2),solver='bisect') == 1 + +def test_mnewton(): + f = lambda x: polyval([1,3,3,1],x) + x = findroot(f, -0.9, solver='mnewton') + assert abs(f(x)) < eps + +def test_anewton(): + f = lambda x: (x - 2)**100 + x = findroot(f, 1., solver=ANewton) + assert abs(f(x)) < eps + +def test_muller(): + f = lambda x: (2 + x)**3 + 2 + x = findroot(f, 1., solver=Muller) + assert abs(f(x)) < eps + +def test_multiplicity(): + for i in range(1, 5): + assert multiplicity(lambda x: (x - 1)**i, 1) == i + assert multiplicity(lambda x: x**2, 1) == 0 + +def test_multidimensional(): + def f(*x): + return [3*x[0]**2-2*x[1]**2-1, x[0]**2-2*x[0]+x[1]**2+2*x[1]-8] + assert mnorm(jacobian(f, (1,-2)) - matrix([[6,8],[0,-2]]),1) < 1.e-7 + for x, error in MDNewton(mp, f, (1,-2), verbose=0, + norm=lambda x: norm(x, inf)): + pass + assert norm(f(*x), 2) < 1e-14 + # The Chinese mathematician Zhu Shijie was the very first to solve this + # nonlinear system 700 years ago + f1 = lambda x, y: -x + 2*y + f2 = lambda x, y: (x**2 + x*(y**2 - 2) - 4*y) / (x + 4) + f3 = lambda x, y: sqrt(x**2 + y**2) + def f(x, y): + f1x = f1(x, y) + return (f2(x, y) - f1x, f3(x, y) - f1x) + x = findroot(f, (10, 10)) + assert [int(round(i)) for i in x] == [3, 4] + +def test_trivial(): + assert findroot(lambda x: 0, 1) == 1 + assert findroot(lambda x: x, 0) == 0 + #assert findroot(lambda x, y: x + y, (1, -1)) == (1, -1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_special.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_special.py new file mode 100644 index 0000000000000000000000000000000000000000..30825abd89ada00f937260cb51ef649546be7021 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_special.py @@ -0,0 +1,113 @@ +from mpmath import * + +def test_special(): + assert inf == inf + assert inf != -inf + assert -inf == -inf + assert inf != nan + assert nan != nan + assert isnan(nan) + assert --inf == inf + assert abs(inf) == inf + assert abs(-inf) == inf + assert abs(nan) != abs(nan) + + assert isnan(inf - inf) + assert isnan(inf + (-inf)) + assert isnan(-inf - (-inf)) + + assert isnan(inf + nan) + assert isnan(-inf + nan) + + assert mpf(2) + inf == inf + assert 2 + inf == inf + assert mpf(2) - inf == -inf + assert 2 - inf == -inf + + assert inf > 3 + assert 3 < inf + assert 3 > -inf + assert -inf < 3 + assert inf > mpf(3) + assert mpf(3) < inf + assert mpf(3) > -inf + assert -inf < mpf(3) + + assert not (nan < 3) + assert not (nan > 3) + + assert isnan(inf * 0) + assert isnan(-inf * 0) + assert inf * 3 == inf + assert inf * -3 == -inf + assert -inf * 3 == -inf + assert -inf * -3 == inf + assert inf * inf == inf + assert -inf * -inf == inf + + assert isnan(nan / 3) + assert inf / -3 == -inf + assert inf / 3 == inf + assert 3 / inf == 0 + assert -3 / inf == 0 + assert 0 / inf == 0 + assert isnan(inf / inf) + assert isnan(inf / -inf) + assert isnan(inf / nan) + + assert mpf('inf') == mpf('+inf') == inf + assert mpf('-inf') == -inf + assert isnan(mpf('nan')) + + assert isinf(inf) + assert isinf(-inf) + assert not isinf(mpf(0)) + assert not isinf(nan) + +def test_special_powers(): + assert inf**3 == inf + assert isnan(inf**0) + assert inf**-3 == 0 + assert (-inf)**2 == inf + assert (-inf)**3 == -inf + assert isnan((-inf)**0) + assert (-inf)**-2 == 0 + assert (-inf)**-3 == 0 + assert isnan(nan**5) + assert isnan(nan**0) + +def test_functions_special(): + assert exp(inf) == inf + assert exp(-inf) == 0 + assert isnan(exp(nan)) + assert log(inf) == inf + assert isnan(log(nan)) + assert isnan(sin(inf)) + assert isnan(sin(nan)) + assert atan(inf).ae(pi/2) + assert atan(-inf).ae(-pi/2) + assert isnan(sqrt(nan)) + assert sqrt(inf) == inf + +def test_convert_special(): + float_inf = 1e300 * 1e300 + float_ninf = -float_inf + float_nan = float_inf/float_ninf + assert mpf(3) * float_inf == inf + assert mpf(3) * float_ninf == -inf + assert isnan(mpf(3) * float_nan) + assert not (mpf(3) < float_nan) + assert not (mpf(3) > float_nan) + assert not (mpf(3) <= float_nan) + assert not (mpf(3) >= float_nan) + assert float(mpf('1e1000')) == float_inf + assert float(mpf('-1e1000')) == float_ninf + assert float(mpf('1e100000000000000000')) == float_inf + assert float(mpf('-1e100000000000000000')) == float_ninf + assert float(mpf('1e-100000000000000000')) == 0.0 + +def test_div_bug(): + assert isnan(nan/1) + assert isnan(nan/2) + assert inf/2 == inf + assert (-inf)/2 == -inf diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_str.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_str.py new file mode 100644 index 0000000000000000000000000000000000000000..569244f252c057ec1029b7efbd8b0ffbfbc47522 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_str.py @@ -0,0 +1,14 @@ +from mpmath import nstr, matrix, inf + +def test_nstr(): + m = matrix([[0.75, 0.190940654, -0.0299195971], + [0.190940654, 0.65625, 0.205663228], + [-0.0299195971, 0.205663228, 0.64453125e-20]]) + assert nstr(m, 4, min_fixed=-inf) == \ + '''[ 0.75 0.1909 -0.02992] +[ 0.1909 0.6563 0.2057] +[-0.02992 0.2057 0.000000000000000000006445]''' + assert nstr(m, 4) == \ + '''[ 0.75 0.1909 -0.02992] +[ 0.1909 0.6563 0.2057] +[-0.02992 0.2057 6.445e-21]''' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_summation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_summation.py new file mode 100644 index 0000000000000000000000000000000000000000..04ffd29f994e1e6310678eec292c0e03f2d6c725 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_summation.py @@ -0,0 +1,53 @@ +from mpmath import * + +def test_sumem(): + mp.dps = 15 + assert sumem(lambda k: 1/k**2.5, [50, 100]).ae(0.0012524505324784962) + assert sumem(lambda k: k**4 + 3*k + 1, [10, 100]).ae(2050333103) + +def test_nsum(): + mp.dps = 15 + assert nsum(lambda x: x**2, [1, 3]) == 14 + assert nsum(lambda k: 1/factorial(k), [0, inf]).ae(e) + assert nsum(lambda k: (-1)**(k+1) / k, [1, inf]).ae(log(2)) + assert nsum(lambda k: (-1)**(k+1) / k**2, [1, inf]).ae(pi**2 / 12) + assert nsum(lambda k: (-1)**k / log(k), [2, inf]).ae(0.9242998972229388) + assert nsum(lambda k: 1/k**2, [1, inf]).ae(pi**2 / 6) + assert nsum(lambda k: 2**k/fac(k), [0, inf]).ae(exp(2)) + assert nsum(lambda k: 1/k**2, [4, inf], method='e').ae(0.2838229557371153) + assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf]) - 1.082323233711138) < 1e-5 + assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf], method='e') - 1.082323233711138) < 1e-4 + +def test_nprod(): + mp.dps = 15 + assert nprod(lambda k: exp(1/k**2), [1,inf], method='r').ae(exp(pi**2/6)) + assert nprod(lambda x: x**2, [1, 3]) == 36 + +def test_fsum(): + mp.dps = 15 + assert fsum([]) == 0 + assert fsum([-4]) == -4 + assert fsum([2,3]) == 5 + assert fsum([1e-100,1]) == 1 + assert fsum([1,1e-100]) == 1 + assert fsum([1e100,1]) == 1e100 + assert fsum([1,1e100]) == 1e100 + assert fsum([1e-100,0]) == 1e-100 + assert fsum([1e-100,1e100,1e-100]) == 1e100 + assert fsum([2,1+1j,1]) == 4+1j + assert fsum([2,inf,3]) == inf + assert fsum([2,-1], absolute=1) == 3 + assert fsum([2,-1], squared=1) == 5 + assert fsum([1,1+j], squared=1) == 1+2j + assert fsum([1,3+4j], absolute=1) == 6 + assert fsum([1,2+3j], absolute=1, squared=1) == 14 + assert isnan(fsum([inf,-inf])) + assert fsum([inf,-inf], absolute=1) == inf + assert fsum([inf,-inf], squared=1) == inf + assert fsum([inf,-inf], absolute=1, squared=1) == inf + assert iv.fsum([1,mpi(2,3)]) == mpi(3,4) + +def test_fprod(): + mp.dps = 15 + assert fprod([]) == 1 + assert fprod([2,3]) == 6 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_trig.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_trig.py new file mode 100644 index 0000000000000000000000000000000000000000..c70a2a0ff4c44c784404ecdb15357d5b91a992d6 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_trig.py @@ -0,0 +1,136 @@ +from mpmath import * +from mpmath.libmp import * + +def test_trig_misc_hard(): + mp.prec = 53 + # Worst-case input for an IEEE double, from a paper by Kahan + x = ldexp(6381956970095103,797) + assert cos(x) == mpf('-4.6871659242546277e-19') + assert sin(x) == 1 + + mp.prec = 150 + a = mpf(10**50) + mp.prec = 53 + assert sin(a).ae(-0.7896724934293100827) + assert cos(a).ae(-0.6135286082336635622) + + # Check relative accuracy close to x = zero + assert sin(1e-100) == 1e-100 # when rounding to nearest + assert sin(1e-6).ae(9.999999999998333e-007, rel_eps=2e-15, abs_eps=0) + assert sin(1e-6j).ae(1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0) + assert sin(-1e-6j).ae(-1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0) + assert cos(1e-100) == 1 + assert cos(1e-6).ae(0.9999999999995) + assert cos(-1e-6j).ae(1.0000000000005) + assert tan(1e-100) == 1e-100 + assert tan(1e-6).ae(1.0000000000003335e-006, rel_eps=2e-15, abs_eps=0) + assert tan(1e-6j).ae(9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0) + assert tan(-1e-6j).ae(-9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0) + +def test_trig_near_zero(): + mp.dps = 15 + + for r in [round_nearest, round_down, round_up, round_floor, round_ceiling]: + assert sin(0, rounding=r) == 0 + assert cos(0, rounding=r) == 1 + + a = mpf('1e-100') + b = mpf('-1e-100') + + assert sin(a, rounding=round_nearest) == a + assert sin(a, rounding=round_down) < a + assert sin(a, rounding=round_floor) < a + assert sin(a, rounding=round_up) >= a + assert sin(a, rounding=round_ceiling) >= a + assert sin(b, rounding=round_nearest) == b + assert sin(b, rounding=round_down) > b + assert sin(b, rounding=round_floor) <= b + assert sin(b, rounding=round_up) <= b + assert sin(b, rounding=round_ceiling) > b + + assert cos(a, rounding=round_nearest) == 1 + assert cos(a, rounding=round_down) < 1 + assert cos(a, rounding=round_floor) < 1 + assert cos(a, rounding=round_up) == 1 + assert cos(a, rounding=round_ceiling) == 1 + assert cos(b, rounding=round_nearest) == 1 + assert cos(b, rounding=round_down) < 1 + assert cos(b, rounding=round_floor) < 1 + assert cos(b, rounding=round_up) == 1 + assert cos(b, rounding=round_ceiling) == 1 + + +def test_trig_near_n_pi(): + + mp.dps = 15 + a = [n*pi for n in [1, 2, 6, 11, 100, 1001, 10000, 100001]] + mp.dps = 135 + a.append(10**100 * pi) + mp.dps = 15 + + assert sin(a[0]) == mpf('1.2246467991473531772e-16') + assert sin(a[1]) == mpf('-2.4492935982947063545e-16') + assert sin(a[2]) == mpf('-7.3478807948841190634e-16') + assert sin(a[3]) == mpf('4.8998251578625894243e-15') + assert sin(a[4]) == mpf('1.9643867237284719452e-15') + assert sin(a[5]) == mpf('-8.8632615209684813458e-15') + assert sin(a[6]) == mpf('-4.8568235395684898392e-13') + assert sin(a[7]) == mpf('3.9087342299491231029e-11') + assert sin(a[8]) == mpf('-1.369235466754566993528e-36') + + r = round_nearest + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) == 1 + + r = round_up + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) == 1 + + r = round_down + assert cos(a[0], rounding=r) > -1 + assert cos(a[1], rounding=r) < 1 + assert cos(a[2], rounding=r) < 1 + assert cos(a[3], rounding=r) > -1 + assert cos(a[4], rounding=r) < 1 + assert cos(a[5], rounding=r) > -1 + assert cos(a[6], rounding=r) < 1 + assert cos(a[7], rounding=r) > -1 + assert cos(a[8], rounding=r) < 1 + + r = round_floor + assert cos(a[0], rounding=r) == -1 + assert cos(a[1], rounding=r) < 1 + assert cos(a[2], rounding=r) < 1 + assert cos(a[3], rounding=r) == -1 + assert cos(a[4], rounding=r) < 1 + assert cos(a[5], rounding=r) == -1 + assert cos(a[6], rounding=r) < 1 + assert cos(a[7], rounding=r) == -1 + assert cos(a[8], rounding=r) < 1 + + r = round_ceiling + assert cos(a[0], rounding=r) > -1 + assert cos(a[1], rounding=r) == 1 + assert cos(a[2], rounding=r) == 1 + assert cos(a[3], rounding=r) > -1 + assert cos(a[4], rounding=r) == 1 + assert cos(a[5], rounding=r) > -1 + assert cos(a[6], rounding=r) == 1 + assert cos(a[7], rounding=r) > -1 + assert cos(a[8], rounding=r) == 1 + + mp.dps = 15 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_visualization.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_visualization.py new file mode 100644 index 0000000000000000000000000000000000000000..81ffd05194322f00e4c75dc02bc862b383468bff --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/test_visualization.py @@ -0,0 +1,32 @@ +""" +Limited tests of the visualization module. Right now it just makes +sure that passing custom Axes works. + +""" + +from mpmath import mp, fp + +def test_axes(): + try: + import matplotlib + version = matplotlib.__version__.split("-")[0] + version = version.split(".")[:2] + if [int(_) for _ in version] < [0,99]: + raise ImportError + import pylab + except ImportError: + print("\nSkipping test (pylab not available or too old version)\n") + return + fig = pylab.figure() + axes = fig.add_subplot(111) + for ctx in [mp, fp]: + ctx.plot(lambda x: x**2, [0, 3], axes=axes) + assert axes.get_xlabel() == 'x' + assert axes.get_ylabel() == 'f(x)' + + fig = pylab.figure() + axes = fig.add_subplot(111) + for ctx in [mp, fp]: + ctx.cplot(lambda z: z, [-2, 2], [-10, 10], axes=axes) + assert axes.get_xlabel() == 'Re(z)' + assert axes.get_ylabel() == 'Im(z)' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/torture.py b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/torture.py new file mode 100644 index 0000000000000000000000000000000000000000..845d5c6d7d017e51e1ed9a8fe3106cfa32fd967f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/mpmath/tests/torture.py @@ -0,0 +1,224 @@ +""" +Torture tests for asymptotics and high precision evaluation of +special functions. + +(Other torture tests may also be placed here.) + +Running this file (gmpy recommended!) takes several CPU minutes. +With Python 2.6+, multiprocessing is used automatically to run tests +in parallel if many cores are available. (A single test may take between +a second and several minutes; possibly more.) + +The idea: + +* We evaluate functions at positive, negative, imaginary, 45- and 135-degree + complex values with magnitudes between 10^-20 to 10^20, at precisions between + 5 and 150 digits (we can go even higher for fast functions). + +* Comparing the result from two different precision levels provides + a strong consistency check (particularly for functions that use + different algorithms at different precision levels). + +* That the computation finishes at all (without failure), within reasonable + time, provides a check that evaluation works at all: that the code runs, + that it doesn't get stuck in an infinite loop, and that it doesn't use + some extremely slowly algorithm where it could use a faster one. + +TODO: + +* Speed up those functions that take long to finish! +* Generalize to test more cases; more options. +* Implement a timeout mechanism. +* Some functions are notably absent, including the following: + * inverse trigonometric functions (some become inaccurate for complex arguments) + * ci, si (not implemented properly for large complex arguments) + * zeta functions (need to modify test not to try too large imaginary values) + * and others... + +""" + + +import sys, os +from timeit import default_timer as clock + +if "-nogmpy" in sys.argv: + sys.argv.remove('-nogmpy') + os.environ['MPMATH_NOGMPY'] = 'Y' + +filt = '' +if not sys.argv[-1].endswith(".py"): + filt = sys.argv[-1] + +from mpmath import * +from mpmath.libmp.backend import exec_ + +def test_asymp(f, maxdps=150, verbose=False, huge_range=False): + dps = [5,15,25,50,90,150,500,1500,5000,10000] + dps = [p for p in dps if p <= maxdps] + def check(x,y,p,inpt): + if abs(x-y)/abs(y) < workprec(20)(power)(10, -p+1): + return + print() + print("Error!") + print("Input:", inpt) + print("dps =", p) + print("Result 1:", x) + print("Result 2:", y) + print("Absolute error:", abs(x-y)) + print("Relative error:", abs(x-y)/abs(y)) + raise AssertionError + exponents = range(-20,20) + if huge_range: + exponents += [-1000, -100, -50, 50, 100, 1000] + for n in exponents: + if verbose: + sys.stdout.write(". ") + mp.dps = 25 + xpos = mpf(10)**n / 1.1287 + xneg = -xpos + ximag = xpos*j + xcomplex1 = xpos*(1+j) + xcomplex2 = xpos*(-1+j) + for i in range(len(dps)): + if verbose: + print("Testing dps = %s" % dps[i]) + mp.dps = dps[i] + new = f(xpos), f(xneg), f(ximag), f(xcomplex1), f(xcomplex2) + if i != 0: + p = dps[i-1] + check(prev[0], new[0], p, xpos) + check(prev[1], new[1], p, xneg) + check(prev[2], new[2], p, ximag) + check(prev[3], new[3], p, xcomplex1) + check(prev[4], new[4], p, xcomplex2) + prev = new + if verbose: + print() + +a1, a2, a3, a4, a5 = 1.5, -2.25, 3.125, 4, 2 + +def test_bernoulli_huge(): + p, q = bernfrac(9000) + assert p % 10**10 == 9636701091 + assert q == 4091851784687571609141381951327092757255270 + mp.dps = 15 + assert str(bernoulli(10**100)) == '-2.58183325604736e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623' + mp.dps = 50 + assert str(bernoulli(10**100)) == '-2.5818332560473632073252488656039475548106223822913e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623' + mp.dps = 15 + +cases = """\ +test_bernoulli_huge() +test_asymp(lambda z: +pi, maxdps=10000) +test_asymp(lambda z: +e, maxdps=10000) +test_asymp(lambda z: +ln2, maxdps=10000) +test_asymp(lambda z: +ln10, maxdps=10000) +test_asymp(lambda z: +phi, maxdps=10000) +test_asymp(lambda z: +catalan, maxdps=5000) +test_asymp(lambda z: +euler, maxdps=5000) +test_asymp(lambda z: +glaisher, maxdps=1000) +test_asymp(lambda z: +khinchin, maxdps=1000) +test_asymp(lambda z: +twinprime, maxdps=150) +test_asymp(lambda z: stieltjes(2), maxdps=150) +test_asymp(lambda z: +mertens, maxdps=150) +test_asymp(lambda z: +apery, maxdps=5000) +test_asymp(sqrt, maxdps=10000, huge_range=True) +test_asymp(cbrt, maxdps=5000, huge_range=True) +test_asymp(lambda z: root(z,4), maxdps=5000, huge_range=True) +test_asymp(lambda z: root(z,-5), maxdps=5000, huge_range=True) +test_asymp(exp, maxdps=5000, huge_range=True) +test_asymp(expm1, maxdps=1500) +test_asymp(ln, maxdps=5000, huge_range=True) +test_asymp(cosh, maxdps=5000) +test_asymp(sinh, maxdps=5000) +test_asymp(tanh, maxdps=1500) +test_asymp(sin, maxdps=5000, huge_range=True) +test_asymp(cos, maxdps=5000, huge_range=True) +test_asymp(tan, maxdps=1500) +test_asymp(agm, maxdps=1500, huge_range=True) +test_asymp(ellipk, maxdps=1500) +test_asymp(ellipe, maxdps=1500) +test_asymp(lambertw, huge_range=True) +test_asymp(lambda z: lambertw(z,-1)) +test_asymp(lambda z: lambertw(z,1)) +test_asymp(lambda z: lambertw(z,4)) +test_asymp(gamma) +test_asymp(loggamma) # huge_range=True ? +test_asymp(ei) +test_asymp(e1) +test_asymp(li, huge_range=True) +test_asymp(ci) +test_asymp(si) +test_asymp(chi) +test_asymp(shi) +test_asymp(erf) +test_asymp(erfc) +test_asymp(erfi) +test_asymp(lambda z: besselj(2, z)) +test_asymp(lambda z: bessely(2, z)) +test_asymp(lambda z: besseli(2, z)) +test_asymp(lambda z: besselk(2, z)) +test_asymp(lambda z: besselj(-2.25, z)) +test_asymp(lambda z: bessely(-2.25, z)) +test_asymp(lambda z: besseli(-2.25, z)) +test_asymp(lambda z: besselk(-2.25, z)) +test_asymp(airyai) +test_asymp(airybi) +test_asymp(lambda z: hyp0f1(a1, z)) +test_asymp(lambda z: hyp1f1(a1, a2, z)) +test_asymp(lambda z: hyp1f2(a1, a2, a3, z)) +test_asymp(lambda z: hyp2f0(a1, a2, z)) +test_asymp(lambda z: hyperu(a1, a2, z)) +test_asymp(lambda z: hyp2f1(a1, a2, a3, z)) +test_asymp(lambda z: hyp2f2(a1, a2, a3, a4, z)) +test_asymp(lambda z: hyp2f3(a1, a2, a3, a4, a5, z)) +test_asymp(lambda z: coulombf(a1, a2, z)) +test_asymp(lambda z: coulombg(a1, a2, z)) +test_asymp(lambda z: polylog(2,z)) +test_asymp(lambda z: polylog(3,z)) +test_asymp(lambda z: polylog(-2,z)) +test_asymp(lambda z: expint(4, z)) +test_asymp(lambda z: expint(-4, z)) +test_asymp(lambda z: expint(2.25, z)) +test_asymp(lambda z: gammainc(2.5, z, 5)) +test_asymp(lambda z: gammainc(2.5, 5, z)) +test_asymp(lambda z: hermite(3, z)) +test_asymp(lambda z: hermite(2.5, z)) +test_asymp(lambda z: legendre(3, z)) +test_asymp(lambda z: legendre(4, z)) +test_asymp(lambda z: legendre(2.5, z)) +test_asymp(lambda z: legenp(a1, a2, z)) +test_asymp(lambda z: legenq(a1, a2, z), maxdps=90) # abnormally slow +test_asymp(lambda z: jtheta(1, z, 0.5)) +test_asymp(lambda z: jtheta(2, z, 0.5)) +test_asymp(lambda z: jtheta(3, z, 0.5)) +test_asymp(lambda z: jtheta(4, z, 0.5)) +test_asymp(lambda z: jtheta(1, z, 0.5, 1)) +test_asymp(lambda z: jtheta(2, z, 0.5, 1)) +test_asymp(lambda z: jtheta(3, z, 0.5, 1)) +test_asymp(lambda z: jtheta(4, z, 0.5, 1)) +test_asymp(barnesg, maxdps=90) +""" + +def testit(line): + if filt in line: + print(line) + t1 = clock() + exec_(line, globals(), locals()) + t2 = clock() + elapsed = t2-t1 + print("Time:", elapsed, "for", line, "(OK)") + +if __name__ == '__main__': + try: + from multiprocessing import Pool + mapf = Pool(None).map + print("Running tests with multiprocessing") + except ImportError: + print("Not using multiprocessing") + mapf = map + t1 = clock() + tasks = cases.splitlines() + mapf(testit, tasks) + t2 = clock() + print("Cumulative wall time:", t2-t1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx-3.6.1.dist-info/licenses/LICENSE.txt b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx-3.6.1.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..0bf9a8f3f4922a0b70aac95ac7aab9574975342b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx-3.6.1.dist-info/licenses/LICENSE.txt @@ -0,0 +1,37 @@ +NetworkX is distributed with the 3-clause BSD license. + +:: + + Copyright (c) 2004-2025, NetworkX Developers + Aric Hagberg + Dan Schult + Pieter Swart + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NetworkX Developers nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c02bb2f949b96d650f0a89e553bbe08a0f6e1dd0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/asteroidal.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/asteroidal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9e6171da46884286a5ce1c25269918dc4bc0e06 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/asteroidal.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/boundary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/boundary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4089094c5fafe336b4276f525d00d8fd5c2b4ef Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/boundary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/bridges.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/bridges.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dab97a0477722e01eff41d2e111b55dee07436e6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/bridges.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/broadcasting.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/broadcasting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be7d491ece600c2d18cc24720a90b7b78715ab21 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/broadcasting.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chains.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chains.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..261742651e22b049b5d4049b155377eb011e4b74 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chains.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chordal.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chordal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ac32d82c7889439a4c34c86b824b0c06ce743ab Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/chordal.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/clique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/clique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54770d7a4be74101b2a98f04dedd28ab4c4abf2f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/clique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cluster.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cluster.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1fa27fa3277cec21fcc092c9c9da32c9e66b961 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cluster.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/communicability_alg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/communicability_alg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..beb85fcbd7e1cd611ce84c67e5d7e812c11afe4d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/communicability_alg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/core.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de38e5f28ba92e6d2a1abcd54c0ae585d659e73a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/core.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/covering.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/covering.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe8e9c46ceed15f68d1679c754f44af576acf20a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/covering.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cuts.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cuts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c85b8a50665da51505ef4e091d5f1e01cd434412 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cuts.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cycles.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cycles.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bec1635d3012508de3a6c1c07fb1b7307fb87f13 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/cycles.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/d_separation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/d_separation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d3fae71849599033e86e8628031b5abcc82b805 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/d_separation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dag.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc01bb9622828a30fdfc35a4df5125a9a5e9c715 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dag.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6c2163f7b08019be386c920dfa8e65098fdc931 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_regular.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_regular.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46b1a842bacc88dabb99b4faa1791d8374789adc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/distance_regular.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominance.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4e70ab905cb1748aca67f0a9ff52f73c3c3b30f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominance.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominating.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominating.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50796d3825cda4096c6e287932b7016dc9888bc8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/dominating.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/efficiency_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/efficiency_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..939c0223ad51daadf37456f21a62bb17a2030ad2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/efficiency_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/euler.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/euler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07489c3daefaf20aad20c9a11564aea838349a96 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/euler.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graph_hashing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graph_hashing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26be130439cad27b4c733eeab4481deb989e95a4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graph_hashing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graphical.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graphical.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ec7163e3c6dee190f0ad6d5605b1fa3f35d2be6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/graphical.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hierarchy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hierarchy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13d5ab2471519e9ef092c114c0ea03221f1f91a1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hierarchy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hybrid.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hybrid.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01007a04811165cf7fdffc73e6d313d571ac130b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/hybrid.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/isolate.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/isolate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74823fce5f704e32b5da4b153da56843612d727a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/isolate.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/link_prediction.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/link_prediction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77d4612b1afef22652ae1f91ccc40049e12e13f9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/link_prediction.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/lowest_common_ancestors.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/lowest_common_ancestors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e522db11402eeac6d28720c7d4f1c7aba08197db Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/lowest_common_ancestors.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da93287b9f97678f7f2bc08f5262b73802023dea Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/mis.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/mis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a3b5c562cf968afea1b67aa0cc0a6d3b9181e14 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/mis.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/moral.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/moral.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f83f2f9ce25a60ccbf041eb1a23cb1c2c69fc82 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/moral.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/node_classification.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/node_classification.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5616be2f27d3f5ba0aefa2b9ec257145baf7be0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/node_classification.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/non_randomness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/non_randomness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..989e1fadab86aea8af781eadbdebe77174360fdb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/non_randomness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/perfect_graph.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/perfect_graph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3da4a9edcaf8a12d6354456cab6b87aae36beee8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/perfect_graph.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planar_drawing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planar_drawing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ec6f4b9b945aa9cde14ebf6d5f7bf1e92304d41 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planar_drawing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planarity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planarity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7acb08e309b44ccb32858f01c889291eb7ee54be Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/planarity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/polynomials.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/polynomials.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4142807aaf3d4b3ea379faa319471374cd63a222 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/polynomials.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/reciprocity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/reciprocity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43458a857bc9fa84675b536ed57c0f0a423f24a1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/reciprocity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/regular.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/regular.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8db34e7fa6860d05b975bcb4f269f33418afb52 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/regular.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/richclub.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/richclub.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..503ee720d42db3235966403f7a3f8fbb81d802ab Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/richclub.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/similarity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/similarity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4835a5d1e32e0abd3411b49fd15a8ea5ffc660a3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/similarity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/simple_paths.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/simple_paths.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f4835249a59e2f3fddbc52fe29514e84044c2682 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/simple_paths.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smallworld.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smallworld.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9ce664756fc509baa2e88291679db8fa62cc4af Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smallworld.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smetric.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smetric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..717c5bd83a77de13f1477c99f5a6a827b2b7733b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/smetric.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/sparsifiers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/sparsifiers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3458bdd61055116e93d02b057de36c532d99574 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/sparsifiers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/structuralholes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/structuralholes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da88a582a93501eb2da2a99872cae43a99380f0f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/structuralholes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/summarization.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/summarization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..513b5ec6a1d0ac422874d9914f452b56dfe8956a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/summarization.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/swap.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/swap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc420640839d0a21398ea1a888c9805db9f62860 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/swap.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/threshold.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/threshold.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..869c3a1c8ccdfc63bd293824a06336d7e4279811 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/threshold.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/time_dependent.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/time_dependent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee3810c0fb28b2a444e80d2cf0a38e7ddf4c09dd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/time_dependent.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/tournament.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/tournament.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92dc30b4b49b874bd437d54df78c713d50d9f90d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/tournament.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/triads.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/triads.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6f5531d6a56c1d12ad859856ae7a0a134a4a94d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/triads.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/vitality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/vitality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b4a3410e0caefdd99ca52888b204b7c3d8e0599 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/vitality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/voronoi.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/voronoi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20e722d9dc3e1b69505f0c3489ad5613eea8d8dd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/voronoi.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/walks.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/walks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1d939a0c15365763a790d7b750b1aa740245ceb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/walks.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/wiener.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/wiener.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1325c377e310bfa7a18a11ad23c9084b9c2eb0e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/__pycache__/wiener.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b0b401583eb2a31ca600efd9cfdf43839e098a28 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__init__.py @@ -0,0 +1,26 @@ +"""Approximations of graph properties and Heuristic methods for optimization. + +The functions in this class are not imported into the top-level ``networkx`` +namespace so the easiest way to use them is with:: + + >>> from networkx.algorithms import approximation + +Another option is to import the specific function with +``from networkx.algorithms.approximation import function_name``. + +""" + +from networkx.algorithms.approximation.clustering_coefficient import * +from networkx.algorithms.approximation.clique import * +from networkx.algorithms.approximation.connectivity import * +from networkx.algorithms.approximation.distance_measures import * +from networkx.algorithms.approximation.dominating_set import * +from networkx.algorithms.approximation.kcomponents import * +from networkx.algorithms.approximation.matching import * +from networkx.algorithms.approximation.ramsey import * +from networkx.algorithms.approximation.steinertree import * +from networkx.algorithms.approximation.traveling_salesman import * +from networkx.algorithms.approximation.treewidth import * +from networkx.algorithms.approximation.vertex_cover import * +from networkx.algorithms.approximation.maxcut import * +from networkx.algorithms.approximation.density import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffd19ff201ac7b69965ae49052221e242b4b355b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2ed99fe4afc67c292a217e4fde6abf150851a89 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clustering_coefficient.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clustering_coefficient.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a0baec11e686e539831032060edd858af9f1654b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/clustering_coefficient.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b19fa1c94ff2c7bfadcb1e14f6878d1d4273ed7c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/density.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/density.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3872c53de384ea5d57e05c20aebacc4f4e2d467 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/density.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be99bb830b28ddea52c92b145209f2ea806c9e39 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/dominating_set.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/dominating_set.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8ed535c49ead18fb079fb63b33bdb3ba8e2e67b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/dominating_set.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..918fe61be12085a1883de78cc9751787a87c885d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4718cf807dad3120e871a55a745508a14f9cd1f7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/maxcut.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/maxcut.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..042a1519031d7cdb3f40565d2658eda3f6c46796 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/maxcut.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/ramsey.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/ramsey.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d79c0d0886513b4604f30a22499942530339f8df Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/ramsey.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/steinertree.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/steinertree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0b5f4e632b6d80e9c183486600c7cb0c98d72ae Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/steinertree.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/traveling_salesman.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/traveling_salesman.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4294cbaa0a22f0255b1750000a702df63be9d579 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/traveling_salesman.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/treewidth.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/treewidth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8744872f40c3600f9f550cd4bdf687ff0d150b8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/treewidth.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/vertex_cover.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/vertex_cover.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8cafaa98720481a4bfb2b54898d2c1d9e51025bd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/__pycache__/vertex_cover.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clique.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clique.py new file mode 100644 index 0000000000000000000000000000000000000000..ed0f3506369046c749d118c4264afeb4f054f2cd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clique.py @@ -0,0 +1,259 @@ +"""Functions for computing large cliques and maximum independent sets.""" + +import networkx as nx +from networkx.algorithms.approximation import ramsey +from networkx.utils import not_implemented_for + +__all__ = [ + "clique_removal", + "max_clique", + "large_clique_size", + "maximum_independent_set", +] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def maximum_independent_set(G): + """Returns an approximate maximum independent set. + + Independent set or stable set is a set of vertices in a graph, no two of + which are adjacent. That is, it is a set I of vertices such that for every + two vertices in I, there is no edge connecting the two. Equivalently, each + edge in the graph has at most one endpoint in I. The size of an independent + set is the number of vertices it contains [1]_. + + A maximum independent set is a largest independent set for a given graph G + and its size is denoted $\\alpha(G)$. The problem of finding such a set is called + the maximum independent set problem and is an NP-hard optimization problem. + As such, it is unlikely that there exists an efficient algorithm for finding + a maximum independent set of a graph. + + The Independent Set algorithm is based on [2]_. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + iset : Set + The apx-maximum independent set + + Examples + -------- + >>> G = nx.path_graph(10) + >>> nx.approximation.maximum_independent_set(G) + {0, 2, 4, 6, 9} + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + + Notes + ----- + Finds the $O(|V|/(log|V|)^2)$ apx of independent set in the worst case. + + References + ---------- + .. [1] `Wikipedia: Independent set + `_ + .. [2] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + """ + iset, _ = clique_removal(G) + return iset + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def max_clique(G): + r"""Find the Maximum Clique + + Finds the $O(|V|/(log|V|)^2)$ apx of maximum clique/independent set + in the worst case. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + clique : set + The apx-maximum clique of the graph + + Examples + -------- + >>> G = nx.path_graph(10) + >>> nx.approximation.max_clique(G) + {8, 9} + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + + Notes + ----- + A clique in an undirected graph G = (V, E) is a subset of the vertex set + `C \subseteq V` such that for every two vertices in C there exists an edge + connecting the two. This is equivalent to saying that the subgraph + induced by C is complete (in some cases, the term clique may also refer + to the subgraph). + + A maximum clique is a clique of the largest possible size in a given graph. + The clique number `\omega(G)` of a graph G is the number of + vertices in a maximum clique in G. The intersection number of + G is the smallest number of cliques that together cover all edges of G. + + https://en.wikipedia.org/wiki/Maximum_clique + + References + ---------- + .. [1] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + doi:10.1007/BF01994876 + """ + # finding the maximum clique in a graph is equivalent to finding + # the independent set in the complementary graph + cgraph = nx.complement(G) + iset, _ = clique_removal(cgraph) + return iset + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def clique_removal(G): + r"""Repeatedly remove cliques from the graph. + + Results in a $O(|V|/(\log |V|)^2)$ approximation of maximum clique + and independent set. Returns the largest independent set found, along + with found maximal cliques. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + max_ind_cliques : (set, list) tuple + 2-tuple of Maximal Independent Set and list of maximal cliques (sets). + + Examples + -------- + >>> G = nx.path_graph(10) + >>> nx.approximation.clique_removal(G) + ({0, 2, 4, 6, 9}, [{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}]) + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + + References + ---------- + .. [1] Boppana, R., & Halldórsson, M. M. (1992). + Approximating maximum independent sets by excluding subgraphs. + BIT Numerical Mathematics, 32(2), 180–196. Springer. + """ + graph = G.copy() + c_i, i_i = ramsey.ramsey_R2(graph) + cliques = [c_i] + isets = [i_i] + while graph: + graph.remove_nodes_from(c_i) + c_i, i_i = ramsey.ramsey_R2(graph) + if c_i: + cliques.append(c_i) + if i_i: + isets.append(i_i) + # Determine the largest independent set as measured by cardinality. + maxiset = max(isets, key=len) + return maxiset, cliques + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def large_clique_size(G): + """Find the size of a large clique in a graph. + + A *clique* is a subset of nodes in which each pair of nodes is + adjacent. This function is a heuristic for finding the size of a + large clique in the graph. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + k: integer + The size of a large clique in the graph. + + Examples + -------- + >>> G = nx.path_graph(10) + >>> nx.approximation.large_clique_size(G) + 2 + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + + Notes + ----- + This implementation is from [1]_. Its worst case time complexity is + :math:`O(n d^2)`, where *n* is the number of nodes in the graph and + *d* is the maximum degree. + + This function is a heuristic, which means it may work well in + practice, but there is no rigorous mathematical guarantee on the + ratio between the returned number and the actual largest clique size + in the graph. + + References + ---------- + .. [1] Pattabiraman, Bharath, et al. + "Fast Algorithms for the Maximum Clique Problem on Massive Graphs + with Applications to Overlapping Community Detection." + *Internet Mathematics* 11.4-5 (2015): 421--448. + + + See also + -------- + + :func:`networkx.algorithms.approximation.clique.max_clique` + A function that returns an approximate maximum clique with a + guarantee on the approximation ratio. + + :mod:`networkx.algorithms.clique` + Functions for finding the exact maximum clique in a graph. + + """ + degrees = G.degree + + def _clique_heuristic(G, U, size, best_size): + if not U: + return max(best_size, size) + u = max(U, key=degrees) + U.remove(u) + N_prime = {v for v in G[u] if degrees[v] >= best_size} + return _clique_heuristic(G, U & N_prime, size + 1, best_size) + + best_size = 0 + nodes = (u for u in G if degrees[u] >= best_size) + for u in nodes: + neighbors = {v for v in G[u] if degrees[v] >= best_size} + best_size = _clique_heuristic(G, neighbors, 1, best_size) + return best_size diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clustering_coefficient.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clustering_coefficient.py new file mode 100644 index 0000000000000000000000000000000000000000..545fc65533b8d8f44b35498aa7129c97efc0bc52 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/clustering_coefficient.py @@ -0,0 +1,71 @@ +import networkx as nx +from networkx.utils import not_implemented_for, py_random_state + +__all__ = ["average_clustering"] + + +@not_implemented_for("directed") +@py_random_state(2) +@nx._dispatchable(name="approximate_average_clustering") +def average_clustering(G, trials=1000, seed=None): + r"""Estimates the average clustering coefficient of G. + + The local clustering of each node in `G` is the fraction of triangles + that actually exist over all possible triangles in its neighborhood. + The average clustering coefficient of a graph `G` is the mean of + local clusterings. + + This function finds an approximate average clustering coefficient + for G by repeating `n` times (defined in `trials`) the following + experiment: choose a node at random, choose two of its neighbors + at random, and check if they are connected. The approximate + coefficient is the fraction of triangles found over the number + of trials [1]_. + + Parameters + ---------- + G : NetworkX graph + + trials : integer + Number of trials to perform (default 1000). + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + c : float + Approximated average clustering coefficient. + + Examples + -------- + >>> from networkx.algorithms import approximation + >>> G = nx.erdos_renyi_graph(10, 0.2, seed=10) + >>> approximation.average_clustering(G, trials=1000, seed=10) + 0.214 + + Raises + ------ + NetworkXNotImplemented + If G is directed. + + References + ---------- + .. [1] Schank, Thomas, and Dorothea Wagner. Approximating clustering + coefficient and transitivity. Universität Karlsruhe, Fakultät für + Informatik, 2004. + https://doi.org/10.5445/IR/1000001239 + + """ + n = len(G) + triangles = 0 + nodes = list(G) + for i in [int(seed.random() * n) for i in range(trials)]: + nbrs = list(G[nodes[i]]) + if len(nbrs) < 2: + continue + u, v = seed.sample(nbrs, 2) + if u in G[v]: + triangles += 1 + return triangles / trials diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/connectivity.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/connectivity.py new file mode 100644 index 0000000000000000000000000000000000000000..0b596fdf782bbb223e5203fc066bbac157a29b24 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/connectivity.py @@ -0,0 +1,412 @@ +"""Fast approximation for node connectivity""" + +import itertools +from operator import itemgetter + +import networkx as nx + +__all__ = [ + "local_node_connectivity", + "node_connectivity", + "all_pairs_node_connectivity", +] + + +@nx._dispatchable(name="approximate_local_node_connectivity") +def local_node_connectivity(G, source, target, cutoff=None): + """Compute node connectivity between source and target. + + Pairwise or local node connectivity between two distinct and nonadjacent + nodes is the minimum number of nodes that must be removed (minimum + separating cutset) to disconnect them. By Menger's theorem, this is equal + to the number of node independent paths (paths that share no nodes other + than source and target). Which is what we compute in this function. + + This algorithm is a fast approximation that gives an strict lower + bound on the actual number of node independent paths between two nodes [1]_. + It works for both directed and undirected graphs. + + Parameters + ---------- + + G : NetworkX graph + + source : node + Starting node for node connectivity + + target : node + Ending node for node connectivity + + cutoff : integer + Maximum node connectivity to consider. If None, the minimum degree + of source or target is used as a cutoff. Default value None. + + Returns + ------- + k: integer + pairwise node connectivity + + Examples + -------- + >>> # Platonic octahedral graph has node connectivity 4 + >>> # for each non adjacent node pair + >>> from networkx.algorithms import approximation as approx + >>> G = nx.octahedral_graph() + >>> approx.local_node_connectivity(G, 0, 5) + 4 + + Notes + ----- + This algorithm [1]_ finds node independents paths between two nodes by + computing their shortest path using BFS, marking the nodes of the path + found as 'used' and then searching other shortest paths excluding the + nodes marked as used until no more paths exist. It is not exact because + a shortest path could use nodes that, if the path were longer, may belong + to two different node independent paths. Thus it only guarantees an + strict lower bound on node connectivity. + + Note that the authors propose a further refinement, losing accuracy and + gaining speed, which is not implemented yet. + + See also + -------- + all_pairs_node_connectivity + node_connectivity + + References + ---------- + .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for + Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 + http://eclectic.ss.uci.edu/~drwhite/working.pdf + + """ + if target == source: + raise nx.NetworkXError("source and target have to be different nodes.") + + # Maximum possible node independent paths + if G.is_directed(): + possible = min(G.out_degree(source), G.in_degree(target)) + else: + possible = min(G.degree(source), G.degree(target)) + + K = 0 + if not possible: + return K + + if cutoff is None: + cutoff = float("inf") + + exclude = set() + for i in range(min(possible, cutoff)): + try: + path = _bidirectional_shortest_path(G, source, target, exclude) + exclude.update(set(path)) + K += 1 + except nx.NetworkXNoPath: + break + + return K + + +@nx._dispatchable(name="approximate_node_connectivity") +def node_connectivity(G, s=None, t=None): + r"""Returns an approximation for node connectivity for a graph or digraph G. + + Node connectivity is equal to the minimum number of nodes that + must be removed to disconnect G or render it trivial. By Menger's theorem, + this is equal to the number of node independent paths (paths that + share no nodes other than source and target). + + If source and target nodes are provided, this function returns the + local node connectivity: the minimum number of nodes that must be + removed to break all paths from source to target in G. + + This algorithm is based on a fast approximation that gives an strict lower + bound on the actual number of node independent paths between two nodes [1]_. + It works for both directed and undirected graphs. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + s : node + Source node. Optional. Default value: None. + + t : node + Target node. Optional. Default value: None. + + Returns + ------- + K : integer + Node connectivity of G, or local node connectivity if source + and target are provided. + + Examples + -------- + >>> # Platonic octahedral graph is 4-node-connected + >>> from networkx.algorithms import approximation as approx + >>> G = nx.octahedral_graph() + >>> approx.node_connectivity(G) + 4 + + Notes + ----- + This algorithm [1]_ finds node independents paths between two nodes by + computing their shortest path using BFS, marking the nodes of the path + found as 'used' and then searching other shortest paths excluding the + nodes marked as used until no more paths exist. It is not exact because + a shortest path could use nodes that, if the path were longer, may belong + to two different node independent paths. Thus it only guarantees an + strict lower bound on node connectivity. + + See also + -------- + all_pairs_node_connectivity + local_node_connectivity + + References + ---------- + .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for + Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 + http://eclectic.ss.uci.edu/~drwhite/working.pdf + + """ + if (s is not None and t is None) or (s is None and t is not None): + raise nx.NetworkXError("Both source and target must be specified.") + + # Local node connectivity + if s is not None and t is not None: + if s not in G: + raise nx.NetworkXError(f"node {s} not in graph") + if t not in G: + raise nx.NetworkXError(f"node {t} not in graph") + return local_node_connectivity(G, s, t) + + # Global node connectivity + if G.is_directed(): + connected_func = nx.is_weakly_connected + iter_func = itertools.permutations + + def neighbors(v): + return itertools.chain(G.predecessors(v), G.successors(v)) + + else: + connected_func = nx.is_connected + iter_func = itertools.combinations + neighbors = G.neighbors + + if not connected_func(G): + return 0 + + # Choose a node with minimum degree + v, minimum_degree = min(G.degree(), key=itemgetter(1)) + # Node connectivity is bounded by minimum degree + K = minimum_degree + # compute local node connectivity with all non-neighbors nodes + # and store the minimum + for w in set(G) - set(neighbors(v)) - {v}: + K = min(K, local_node_connectivity(G, v, w, cutoff=K)) + # Same for non adjacent pairs of neighbors of v + for x, y in iter_func(neighbors(v), 2): + if y not in G[x] and x != y: + K = min(K, local_node_connectivity(G, x, y, cutoff=K)) + return K + + +@nx._dispatchable(name="approximate_all_pairs_node_connectivity") +def all_pairs_node_connectivity(G, nbunch=None, cutoff=None): + """Compute node connectivity between all pairs of nodes. + + Pairwise or local node connectivity between two distinct and nonadjacent + nodes is the minimum number of nodes that must be removed (minimum + separating cutset) to disconnect them. By Menger's theorem, this is equal + to the number of node independent paths (paths that share no nodes other + than source and target). Which is what we compute in this function. + + This algorithm is a fast approximation that gives an strict lower + bound on the actual number of node independent paths between two nodes [1]_. + It works for both directed and undirected graphs. + + + Parameters + ---------- + G : NetworkX graph + + nbunch: container + Container of nodes. If provided node connectivity will be computed + only over pairs of nodes in nbunch. + + cutoff : integer + Maximum node connectivity to consider. If None, the minimum degree + of source or target is used as a cutoff in each pair of nodes. + Default value None. + + Returns + ------- + K : dictionary + Dictionary, keyed by source and target, of pairwise node connectivity + + Examples + -------- + A 3 node cycle with one extra node attached has connectivity 2 between all + nodes in the cycle and connectivity 1 between the extra node and the rest: + + >>> G = nx.cycle_graph(3) + >>> G.add_edge(2, 3) + >>> import pprint # for nice dictionary formatting + >>> pprint.pprint(nx.all_pairs_node_connectivity(G)) + {0: {1: 2, 2: 2, 3: 1}, + 1: {0: 2, 2: 2, 3: 1}, + 2: {0: 2, 1: 2, 3: 1}, + 3: {0: 1, 1: 1, 2: 1}} + + See Also + -------- + local_node_connectivity + node_connectivity + + References + ---------- + .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for + Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 + http://eclectic.ss.uci.edu/~drwhite/working.pdf + """ + if nbunch is None: + nbunch = G + else: + nbunch = set(nbunch) + + directed = G.is_directed() + if directed: + iter_func = itertools.permutations + else: + iter_func = itertools.combinations + + all_pairs = {n: {} for n in nbunch} + + for u, v in iter_func(nbunch, 2): + k = local_node_connectivity(G, u, v, cutoff=cutoff) + all_pairs[u][v] = k + if not directed: + all_pairs[v][u] = k + + return all_pairs + + +def _bidirectional_shortest_path(G, source, target, exclude): + """Returns shortest path between source and target ignoring nodes in the + container 'exclude'. + + Parameters + ---------- + + G : NetworkX graph + + source : node + Starting node for path + + target : node + Ending node for path + + exclude: container + Container for nodes to exclude from the search for shortest paths + + Returns + ------- + path: list + Shortest path between source and target ignoring nodes in 'exclude' + + Raises + ------ + NetworkXNoPath + If there is no path or if nodes are adjacent and have only one path + between them + + Notes + ----- + This function and its helper are originally from + networkx.algorithms.shortest_paths.unweighted and are modified to + accept the extra parameter 'exclude', which is a container for nodes + already used in other paths that should be ignored. + + References + ---------- + .. [1] White, Douglas R., and Mark Newman. 2001 A Fast Algorithm for + Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 + http://eclectic.ss.uci.edu/~drwhite/working.pdf + + """ + # call helper to do the real work + results = _bidirectional_pred_succ(G, source, target, exclude) + pred, succ, w = results + + # build path from pred+w+succ + path = [] + # from source to w + while w is not None: + path.append(w) + w = pred[w] + path.reverse() + # from w to target + w = succ[path[-1]] + while w is not None: + path.append(w) + w = succ[w] + + return path + + +def _bidirectional_pred_succ(G, source, target, exclude): + # does BFS from both source and target and meets in the middle + # excludes nodes in the container "exclude" from the search + + # handle either directed or undirected + if G.is_directed(): + Gpred = G.predecessors + Gsucc = G.successors + else: + Gpred = G.neighbors + Gsucc = G.neighbors + + # predecessor and successors in search + pred = {source: None} + succ = {target: None} + + # initialize fringes, start with forward + forward_fringe = [source] + reverse_fringe = [target] + + level = 0 + + while forward_fringe and reverse_fringe: + # Make sure that we iterate one step forward and one step backwards + # thus source and target will only trigger "found path" when they are + # adjacent and then they can be safely included in the container 'exclude' + level += 1 + if level % 2 != 0: + this_level = forward_fringe + forward_fringe = [] + for v in this_level: + for w in Gsucc(v): + if w in exclude: + continue + if w not in pred: + forward_fringe.append(w) + pred[w] = v + if w in succ: + return pred, succ, w # found path + else: + this_level = reverse_fringe + reverse_fringe = [] + for v in this_level: + for w in Gpred(v): + if w in exclude: + continue + if w not in succ: + succ[w] = v + reverse_fringe.append(w) + if w in pred: + return pred, succ, w # found path + + raise nx.NetworkXNoPath(f"No path between {source} and {target}.") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/density.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/density.py new file mode 100644 index 0000000000000000000000000000000000000000..b722df22cb6dea549f50400e216049fa8afbde36 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/density.py @@ -0,0 +1,396 @@ +"""Fast algorithms for the densest subgraph problem""" + +import math + +import networkx as nx + +__all__ = ["densest_subgraph"] + + +def _greedy_plus_plus(G, iterations): + if G.number_of_edges() == 0: + return 0.0, set() + if iterations < 1: + raise ValueError( + f"The number of iterations must be an integer >= 1. Provided: {iterations}" + ) + + loads = {node: 0 for node in G.nodes} # Load vector for Greedy++. + best_density = 0.0 # Highest density encountered. + best_subgraph = set() # Nodes of the best subgraph found. + + for _ in range(iterations): + # Initialize heap for fast access to minimum weighted degree. + heap = nx.utils.BinaryHeap() + + # Compute initial weighted degrees and add nodes to the heap. + for node, degree in G.degree: + heap.insert(node, loads[node] + degree) + # Set up tracking for current graph state. + remaining_nodes = set(G.nodes) + num_edges = G.number_of_edges() + current_degrees = dict(G.degree) + + while remaining_nodes: + num_nodes = len(remaining_nodes) + + # Current density of the (implicit) graph + current_density = num_edges / num_nodes + + # Update the best density. + if current_density > best_density: + best_density = current_density + best_subgraph = set(remaining_nodes) + + # Pop the node with the smallest weighted degree. + node, _ = heap.pop() + if node not in remaining_nodes: + continue # Skip nodes already removed. + + # Update the load of the popped node. + loads[node] += current_degrees[node] + + # Update neighbors' degrees and the heap. + for neighbor in G.neighbors(node): + if neighbor in remaining_nodes: + current_degrees[neighbor] -= 1 + num_edges -= 1 + heap.insert(neighbor, loads[neighbor] + current_degrees[neighbor]) + + # Remove the node from the remaining nodes. + remaining_nodes.remove(node) + + return best_density, best_subgraph + + +def _fractional_peeling(G, b, x, node_to_idx, edge_to_idx): + """ + Optimized fractional peeling using NumPy arrays. + + Parameters + ---------- + G : networkx.Graph + The input graph. + b : numpy.ndarray + Induced load vector. + x : numpy.ndarray + Fractional edge values. + node_to_idx : dict + Mapping from node to index. + edge_to_idx : dict + Mapping from edge to index. + + Returns + ------- + best_density : float + The best density found. + best_subgraph : set + The subset of nodes defining the densest subgraph. + """ + heap = nx.utils.BinaryHeap() + + remaining_nodes = set(G.nodes) + + # Initialize heap with b values + for idx, node in enumerate(G): + heap.insert(node, b[idx]) + + num_edges = G.number_of_edges() + + best_density = 0.0 + best_subgraph = set() + + while remaining_nodes: + num_nodes = len(remaining_nodes) + current_density = num_edges / num_nodes + + if current_density > best_density: + best_density = current_density + best_subgraph = set(remaining_nodes) + + # Pop the node with the smallest b + node, _ = heap.pop() + while node not in remaining_nodes: + node, _ = heap.pop() # Clean the heap from stale values + + # Update neighbors b values by subtracting fractional x value + for neighbor in G.neighbors(node): + if neighbor in remaining_nodes: + neighbor_idx = node_to_idx[neighbor] + # Take off fractional value + b[neighbor_idx] -= x[edge_to_idx[(neighbor, node)]] + num_edges -= 1 + heap.insert(neighbor, b[neighbor_idx]) + + remaining_nodes.remove(node) # peel off node + + return best_density, best_subgraph + + +def _fista(G, iterations): + if G.number_of_edges() == 0: + return 0.0, set() + if iterations < 1: + raise ValueError( + f"The number of iterations must be an integer >= 1. Provided: {iterations}" + ) + import numpy as np + + # 1. Node Mapping: Assign a unique index to each node and edge + node_to_idx = {node: idx for idx, node in enumerate(G)} + num_nodes = G.number_of_nodes() + num_undirected_edges = G.number_of_edges() + + # 2. Edge Mapping: Assign a unique index to each bidirectional edge + bidirectional_edges = [(u, v) for u, v in G.edges] + [(v, u) for u, v in G.edges] + edge_to_idx = {edge: idx for idx, edge in enumerate(bidirectional_edges)} + + num_edges = len(bidirectional_edges) + + # 3. Reverse Edge Mapping: Map each (bidirectional) edge to its reverse edge index + reverse_edge_idx = np.empty(num_edges, dtype=np.int32) + for idx in range(num_undirected_edges): + reverse_edge_idx[idx] = num_undirected_edges + idx + for idx in range(num_undirected_edges, 2 * num_undirected_edges): + reverse_edge_idx[idx] = idx - num_undirected_edges + + # 4. Initialize Variables as NumPy Arrays + x = np.full(num_edges, 0.5, dtype=np.float32) + y = x.copy() + z = np.zeros(num_edges, dtype=np.float32) + b = np.zeros(num_nodes, dtype=np.float32) # Induced load vector + tk = 1.0 # Momentum term + + # 5. Precompute Edge Source Indices + edge_src_indices = np.array( + [node_to_idx[u] for u, _ in bidirectional_edges], dtype=np.int32 + ) + + # 6. Compute Learning Rate + max_degree = max(deg for _, deg in G.degree) + # 0.9 for floating point errs when max_degree is very large + learning_rate = 0.9 / max_degree + + # 7. Iterative Updates + for _ in range(iterations): + # 7a. Update b: sum y over outgoing edges for each node + b[:] = 0.0 # Reset b to zero + np.add.at(b, edge_src_indices, y) # b_u = \sum_{v : (u,v) \in E(G)} y_{uv} + + # 7b. Compute z, z_{uv} = y_{uv} - 2 * learning_rate * b_u + z = y - 2.0 * learning_rate * b[edge_src_indices] + + # 7c. Update Momentum Term + tknew = (1.0 + math.sqrt(1 + 4.0 * tk**2)) / 2.0 + + # 7d. Update x in a vectorized manner, x_{uv} = (z_{uv} - z_{vu} + 1.0) / 2.0 + new_xuv = (z - z[reverse_edge_idx] + 1.0) / 2.0 + clamped_x = np.clip(new_xuv, 0.0, 1.0) # Clamp x_{uv} between 0 and 1 + + # Update y using the FISTA update formula (similar to gradient descent) + y = ( + clamped_x + + ((tk - 1.0) / tknew) * (clamped_x - x) + + (tk / tknew) * (clamped_x - y) + ) + + # Update x + x = clamped_x + + # Update tk, the momemntum term + tk = tknew + + # Rebalance the b values! Otherwise performance is a bit suboptimal. + b[:] = 0.0 + np.add.at(b, edge_src_indices, x) # b_u = \sum_{v : (u,v) \in E(G)} x_{uv} + + # Extract the actual (approximate) dense subgraph. + return _fractional_peeling(G, b, x, node_to_idx, edge_to_idx) + + +ALGORITHMS = {"greedy++": _greedy_plus_plus, "fista": _fista} + + +@nx.utils.not_implemented_for("directed") +@nx.utils.not_implemented_for("multigraph") +@nx._dispatchable +def densest_subgraph(G, iterations=1, *, method="fista"): + r"""Returns an approximate densest subgraph for a graph `G`. + + This function runs an iterative algorithm to find the densest subgraph, + and returns both the density and the subgraph. For a discussion on the + notion of density used and the different algorithms available on + networkx, please see the Notes section below. + + Parameters + ---------- + G : NetworkX graph + Undirected graph. + + iterations : int, optional (default=1) + Number of iterations to use for the iterative algorithm. Can be + specified positionally or as a keyword argument. + + method : string, optional (default='fista') + The algorithm to use to approximate the densest subgraph. Supported + options: 'greedy++' by Boob et al. [2]_ and 'fista' by Harb et al. [3]_. + Must be specified as a keyword argument. Other inputs produce a + ValueError. + + Returns + ------- + d : float + The density of the approximate subgraph found. + + S : set + The subset of nodes defining the approximate densest subgraph. + + Examples + -------- + >>> G = nx.star_graph(4) + >>> nx.approximation.densest_subgraph(G, iterations=1) + (0.8, {0, 1, 2, 3, 4}) + + Notes + ----- + **Problem Definition:** + The densest subgraph problem (DSG) asks to find the subgraph + $S \subseteq V(G)$ with maximum density. For a subset of the nodes of + $G$, $S \subseteq V(G)$, define $E(S) = \{ (u,v) : (u,v)\in E(G), + u\in S, v\in S \}$ as the set of edges with both endpoints in $S$. + The density of $S$ is defined as $|E(S)|/|S|$, the ratio between the + edges in the subgraph $G[S]$ and the number of nodes in that subgraph. + Note that this is different from the standard graph theoretic definition + of density, defined as $\frac{2|E(S)|}{|S|(|S|-1)}$, for historical + reasons. + + **Exact Algorithms:** + The densest subgraph problem is polynomial time solvable using maximum + flow, commonly referred to as Goldberg's algorithm. However, the + algorithm is quite involved. It first binary searches on the optimal + density, $d^\ast$. For a guess of the density $d$, it sets up a flow + network $G'$ with size $O(m)$. The maximum flow solution either + informs the algorithm that no subgraph with density $d$ exists, or it + provides a subgraph with density at least $d$. However, this is + inherently bottlenecked by the maximum flow algorithm. For example, [2]_ + notes that Goldberg’s algorithm was not feasible on many large graphs + even though they used a highly optimized maximum flow library. + + **Charikar's Greedy Peeling:** + While exact solution algorithms are quite involved, there are several + known approximation algorithms for the densest subgraph problem. + + Charikar [1]_ described a very simple 1/2-approximation algorithm for DSG + known as the greedy "peeling" algorithm. The algorithm creates an + ordering of the nodes as follows. The first node $v_1$ is the one with + the smallest degree in $G$ (ties broken arbitrarily). It selects + $v_2$ to be the smallest degree node in $G \setminus v_1$. Letting + $G_i$ be the graph after removing $v_1, ..., v_i$ (with $G_0=G$), + the algorithm returns the graph among $G_0, ..., G_n$ with the highest + density. + + **Greedy++:** + Boob et al. [2]_ generalized this algorithm into Greedy++, an iterative + algorithm that runs several rounds of "peeling". In fact, Greedy++ with 1 + iteration is precisely Charikar's algorithm. The algorithm converges to a + $(1-\epsilon)$ approximate densest subgraph in $O(\Delta(G)\log + n/\epsilon^2)$ iterations, where $\Delta(G)$ is the maximum degree, + and $n$ is the number of nodes in $G$. The algorithm also has other + desirable properties as shown by [4]_ and [5]_. + + **FISTA Algorithm:** + Harb et al. [3]_ gave a faster and more scalable algorithm using ideas + from quadratic programming for the densest subgraph, which is based on a + fast iterative shrinkage-thresholding algorithm (FISTA) algorithm. It is + known that computing the densest subgraph can be formulated as the + following convex optimization problem: + + Minimize $\sum_{u \in V(G)} b_u^2$ + + Subject to: + + $b_u = \sum_{v: \{u,v\} \in E(G)} x_{uv}$ for all $u \in V(G)$ + + $x_{uv} + x_{vu} = 1.0$ for all $\{u,v\} \in E(G)$ + + $x_{uv} \geq 0, x_{vu} \geq 0$ for all $\{u,v\} \in E(G)$ + + Here, $x_{uv}$ represents the fraction of edge $\{u,v\}$ assigned to + $u$, and $x_{vu}$ to $v$. + + The FISTA algorithm efficiently solves this convex program using gradient + descent with projections. For a learning rate $\alpha$, the algorithm + does: + + 1. **Initialization**: Set $x^{(0)}_{uv} = x^{(0)}_{vu} = 0.5$ for all + edges as a feasible solution. + + 2. **Gradient Update**: For iteration $k\geq 1$, set + $x^{(k+1)}_{uv} = x^{(k)}_{uv} - 2 \alpha \sum_{v: \{u,v\} \in E(G)} + x^{(k)}_{uv}$. However, now $x^{(k+1)}_{uv}$ might be infeasible! + To ensure feasibility, we project $x^{(k+1)}_{uv}$. + + 3. **Projection to the Feasible Set**: Compute + $b^{(k+1)}_u = \sum_{v: \{u,v\} \in E(G)} x^{(k)}_{uv}$ for all + nodes $u$. Define $z^{(k+1)}_{uv} = x^{(k+1)}_{uv} - 2 \alpha + b^{(k+1)}_u$. Update $x^{(k+1)}_{uv} = + CLAMP((z^{(k+1)}_{uv} - z^{(k+1)}_{vu} + 1.0) / 2.0)$, where + $CLAMP(x) = \max(0, \min(1, x))$. + + With a learning rate of $\alpha=1/\Delta(G)$, where $\Delta(G)$ is + the maximum degree, the algorithm converges to the optimum solution of + the convex program. + + **Fractional Peeling:** + To obtain a **discrete** subgraph, we use fractional peeling, an + adaptation of the standard peeling algorithm which peels the minimum + degree vertex in each iteration, and returns the densest subgraph found + along the way. Here, we instead peel the vertex with the smallest + induced load $b_u$: + + 1. Compute $b_u$ and $x_{uv}$. + + 2. Iteratively remove the vertex with the smallest $b_u$, updating its + neighbors' load by $x_{vu}$. + + Fractional peeling transforms the approximately optimal fractional + values $b_u, x_{uv}$ into a discrete subgraph. Unlike traditional + peeling, which removes the lowest-degree node, this method accounts for + fractional edge contributions from the convex program. + + This approach is both scalable and theoretically sound, ensuring a quick + approximation of the densest subgraph while leveraging fractional load + balancing. + + References + ---------- + .. [1] Charikar, Moses. "Greedy approximation algorithms for finding dense + components in a graph." In International workshop on approximation + algorithms for combinatorial optimization, pp. 84-95. Berlin, Heidelberg: + Springer Berlin Heidelberg, 2000. + + .. [2] Boob, Digvijay, Yu Gao, Richard Peng, Saurabh Sawlani, Charalampos + Tsourakakis, Di Wang, and Junxing Wang. "Flowless: Extracting densest + subgraphs without flow computations." In Proceedings of The Web Conference + 2020, pp. 573-583. 2020. + + .. [3] Harb, Elfarouk, Kent Quanrud, and Chandra Chekuri. "Faster and scalable + algorithms for densest subgraph and decomposition." Advances in Neural + Information Processing Systems 35 (2022): 26966-26979. + + .. [4] Harb, Elfarouk, Kent Quanrud, and Chandra Chekuri. "Convergence to + lexicographically optimal base in a (contra) polymatroid and applications + to densest subgraph and tree packing." arXiv preprint arXiv:2305.02987 + (2023). + + .. [5] Chekuri, Chandra, Kent Quanrud, and Manuel R. Torres. "Densest + subgraph: Supermodularity, iterative peeling, and flow." In Proceedings of + the 2022 Annual ACM-SIAM Symposium on Discrete Algorithms (SODA), pp. + 1531-1555. Society for Industrial and Applied Mathematics, 2022. + """ + try: + algo = ALGORITHMS[method] + except KeyError as e: + raise ValueError(f"{method} is not a valid choice for an algorithm.") from e + + return algo(G, iterations) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/distance_measures.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/distance_measures.py new file mode 100644 index 0000000000000000000000000000000000000000..d5847e65a2a401cd607436297fe4c1bbc81db3d9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/distance_measures.py @@ -0,0 +1,150 @@ +"""Distance measures approximated metrics.""" + +import networkx as nx +from networkx.utils.decorators import py_random_state + +__all__ = ["diameter"] + + +@py_random_state(1) +@nx._dispatchable(name="approximate_diameter") +def diameter(G, seed=None): + """Returns a lower bound on the diameter of the graph G. + + The function computes a lower bound on the diameter (i.e., the maximum eccentricity) + of a directed or undirected graph G. The procedure used varies depending on the graph + being directed or not. + + If G is an `undirected` graph, then the function uses the `2-sweep` algorithm [1]_. + The main idea is to pick the farthest node from a random node and return its eccentricity. + + Otherwise, if G is a `directed` graph, the function uses the `2-dSweep` algorithm [2]_, + The procedure starts by selecting a random source node $s$ from which it performs a + forward and a backward BFS. Let $a_1$ and $a_2$ be the farthest nodes in the forward and + backward cases, respectively. Then, it computes the backward eccentricity of $a_1$ using + a backward BFS and the forward eccentricity of $a_2$ using a forward BFS. + Finally, it returns the best lower bound between the two. + + In both cases, the time complexity is linear with respect to the size of G. + + Parameters + ---------- + G : NetworkX graph + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + d : integer + Lower Bound on the Diameter of G + + Examples + -------- + >>> G = nx.path_graph(10) # undirected graph + >>> nx.diameter(G) + 9 + >>> G = nx.cycle_graph(3, create_using=nx.DiGraph) # directed graph + >>> nx.diameter(G) + 2 + + Raises + ------ + NetworkXError + If the graph is empty or + If the graph is undirected and not connected or + If the graph is directed and not strongly connected. + + See Also + -------- + networkx.algorithms.distance_measures.diameter + + References + ---------- + .. [1] Magnien, Clémence, Matthieu Latapy, and Michel Habib. + *Fast computation of empirically tight bounds for the diameter of massive graphs.* + Journal of Experimental Algorithmics (JEA), 2009. + https://arxiv.org/pdf/0904.2728.pdf + .. [2] Crescenzi, Pierluigi, Roberto Grossi, Leonardo Lanzi, and Andrea Marino. + *On computing the diameter of real-world directed (weighted) graphs.* + International Symposium on Experimental Algorithms. Springer, Berlin, Heidelberg, 2012. + https://courses.cs.ut.ee/MTAT.03.238/2014_fall/uploads/Main/diameter.pdf + """ + # if G is empty + if not G: + raise nx.NetworkXError("Expected non-empty NetworkX graph!") + # if there's only a node + if G.number_of_nodes() == 1: + return 0 + # if G is directed + if G.is_directed(): + return _two_sweep_directed(G, seed) + # else if G is undirected + return _two_sweep_undirected(G, seed) + + +def _two_sweep_undirected(G, seed): + """Helper function for finding a lower bound on the diameter + for undirected Graphs. + + The idea is to pick the farthest node from a random node + and return its eccentricity. + + ``G`` is a NetworkX undirected graph. + + .. note:: + + ``seed`` is a random.Random or numpy.random.RandomState instance + """ + # select a random source node + source = seed.choice(list(G)) + # get the distances to the other nodes + distances = nx.shortest_path_length(G, source) + # if some nodes have not been visited, then the graph is not connected + if len(distances) != len(G): + raise nx.NetworkXError("Graph not connected.") + # take a node that is (one of) the farthest nodes from the source + *_, node = distances + # return the eccentricity of the node + return nx.eccentricity(G, node) + + +def _two_sweep_directed(G, seed): + """Helper function for finding a lower bound on the diameter + for directed Graphs. + + It implements 2-dSweep, the directed version of the 2-sweep algorithm. + The algorithm follows the following steps. + 1. Select a source node $s$ at random. + 2. Perform a forward BFS from $s$ to select a node $a_1$ at the maximum + distance from the source, and compute $LB_1$, the backward eccentricity of $a_1$. + 3. Perform a backward BFS from $s$ to select a node $a_2$ at the maximum + distance from the source, and compute $LB_2$, the forward eccentricity of $a_2$. + 4. Return the maximum between $LB_1$ and $LB_2$. + + ``G`` is a NetworkX directed graph. + + .. note:: + + ``seed`` is a random.Random or numpy.random.RandomState instance + """ + # get a new digraph G' with the edges reversed in the opposite direction + G_reversed = G.reverse() + # select a random source node + source = seed.choice(list(G)) + # compute forward distances from source + forward_distances = nx.shortest_path_length(G, source) + # compute backward distances from source + backward_distances = nx.shortest_path_length(G_reversed, source) + # if either the source can't reach every node or not every node + # can reach the source, then the graph is not strongly connected + n = len(G) + if len(forward_distances) != n or len(backward_distances) != n: + raise nx.NetworkXError("DiGraph not strongly connected.") + # take a node a_1 at the maximum distance from the source in G + *_, a_1 = forward_distances + # take a node a_2 at the maximum distance from the source in G_reversed + *_, a_2 = backward_distances + # return the max between the backward eccentricity of a_1 and the forward eccentricity of a_2 + return max(nx.eccentricity(G_reversed, a_1), nx.eccentricity(G, a_2)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/dominating_set.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/dominating_set.py new file mode 100644 index 0000000000000000000000000000000000000000..e568a827ff99dad5390e8d7feb886cf92a3a6cad --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/dominating_set.py @@ -0,0 +1,149 @@ +"""Functions for finding node and edge dominating sets. + +A `dominating set`_ for an undirected graph *G* with vertex set *V* +and edge set *E* is a subset *D* of *V* such that every vertex not in +*D* is adjacent to at least one member of *D*. An `edge dominating set`_ +is a subset *F* of *E* such that every edge not in *F* is +incident to an endpoint of at least one edge in *F*. + +.. _dominating set: https://en.wikipedia.org/wiki/Dominating_set +.. _edge dominating set: https://en.wikipedia.org/wiki/Edge_dominating_set + +""" + +import networkx as nx + +from ...utils import not_implemented_for +from ..matching import maximal_matching + +__all__ = ["min_weighted_dominating_set", "min_edge_dominating_set"] + + +# TODO Why doesn't this algorithm work for directed graphs? +@not_implemented_for("directed") +@nx._dispatchable(node_attrs="weight") +def min_weighted_dominating_set(G, weight=None): + r"""Returns a dominating set that approximates the minimum weight node + dominating set. + + Parameters + ---------- + G : NetworkX graph + Undirected graph. + + weight : string + The node attribute storing the weight of an node. If provided, + the node attribute with this key must be a number for each + node. If not provided, each node is assumed to have weight one. + + Returns + ------- + min_weight_dominating_set : set + A set of nodes, the sum of whose weights is no more than `(\log + w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of + each node in the graph and `w(V^*)` denotes the sum of the + weights of each node in the minimum weight dominating set. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 4), (1, 4), (1, 2), (2, 3), (3, 4), (2, 5)]) + >>> nx.approximation.min_weighted_dominating_set(G) + {1, 2, 4} + + Raises + ------ + NetworkXNotImplemented + If G is directed. + + Notes + ----- + This algorithm computes an approximate minimum weighted dominating + set for the graph `G`. The returned solution has weight `(\log + w(V)) w(V^*)`, where `w(V)` denotes the sum of the weights of each + node in the graph and `w(V^*)` denotes the sum of the weights of + each node in the minimum weight dominating set for the graph. + + This implementation of the algorithm runs in $O(m)$ time, where $m$ + is the number of edges in the graph. + + References + ---------- + .. [1] Vazirani, Vijay V. + *Approximation Algorithms*. + Springer Science & Business Media, 2001. + + """ + # The unique dominating set for the null graph is the empty set. + if len(G) == 0: + return set() + + # This is the dominating set that will eventually be returned. + dom_set = set() + + def _cost(node_and_neighborhood): + """Returns the cost-effectiveness of greedily choosing the given + node. + + `node_and_neighborhood` is a two-tuple comprising a node and its + closed neighborhood. + + """ + v, neighborhood = node_and_neighborhood + return G.nodes[v].get(weight, 1) / len(neighborhood - dom_set) + + # This is a set of all vertices not already covered by the + # dominating set. + vertices = set(G) + # This is a dictionary mapping each node to the closed neighborhood + # of that node. + neighborhoods = {v: {v} | set(G[v]) for v in G} + + # Continue until all vertices are adjacent to some node in the + # dominating set. + while vertices: + # Find the most cost-effective node to add, along with its + # closed neighborhood. + dom_node, min_set = min(neighborhoods.items(), key=_cost) + # Add the node to the dominating set and reduce the remaining + # set of nodes to cover. + dom_set.add(dom_node) + del neighborhoods[dom_node] + vertices -= min_set + + return dom_set + + +@nx._dispatchable +def min_edge_dominating_set(G): + r"""Returns minimum cardinality edge dominating set. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + min_edge_dominating_set : set + Returns a set of dominating edges whose size is no more than 2 * OPT. + + Examples + -------- + >>> G = nx.petersen_graph() + >>> nx.approximation.min_edge_dominating_set(G) + {(0, 1), (4, 9), (6, 8), (5, 7), (2, 3)} + + Raises + ------ + ValueError + If the input graph `G` is empty. + + Notes + ----- + The algorithm computes an approximate solution to the edge dominating set + problem. The result is no more than 2 * OPT in terms of size of the set. + Runtime of the algorithm is $O(|E|)$. + """ + if not G: + raise ValueError("Expected non-empty NetworkX graph!") + return maximal_matching(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/kcomponents.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/kcomponents.py new file mode 100644 index 0000000000000000000000000000000000000000..f726a4e686103b2d30d443f21de326685c615bf4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/kcomponents.py @@ -0,0 +1,369 @@ +"""Fast approximation for k-component structure""" + +import itertools +from collections import defaultdict +from collections.abc import Mapping +from functools import cached_property + +import networkx as nx +from networkx.algorithms.approximation import local_node_connectivity +from networkx.exception import NetworkXError +from networkx.utils import not_implemented_for + +__all__ = ["k_components"] + + +@not_implemented_for("directed") +@nx._dispatchable(name="approximate_k_components") +def k_components(G, min_density=0.95): + r"""Returns the approximate k-component structure of a graph G. + + A `k`-component is a maximal subgraph of a graph G that has, at least, + node connectivity `k`: we need to remove at least `k` nodes to break it + into more components. `k`-components have an inherent hierarchical + structure because they are nested in terms of connectivity: a connected + graph can contain several 2-components, each of which can contain + one or more 3-components, and so forth. + + This implementation is based on the fast heuristics to approximate + the `k`-component structure of a graph [1]_. Which, in turn, it is based on + a fast approximation algorithm for finding good lower bounds of the number + of node independent paths between two nodes [2]_. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + min_density : Float + Density relaxation threshold. Default value 0.95 + + Returns + ------- + k_components : dict + Dictionary with connectivity level `k` as key and a list of + sets of nodes that form a k-component of level `k` as values. + + Raises + ------ + NetworkXNotImplemented + If G is directed. + + Examples + -------- + >>> # Petersen graph has 10 nodes and it is triconnected, thus all + >>> # nodes are in a single component on all three connectivity levels + >>> from networkx.algorithms import approximation as apxa + >>> G = nx.petersen_graph() + >>> k_components = apxa.k_components(G) + + Notes + ----- + The logic of the approximation algorithm for computing the `k`-component + structure [1]_ is based on repeatedly applying simple and fast algorithms + for `k`-cores and biconnected components in order to narrow down the + number of pairs of nodes over which we have to compute White and Newman's + approximation algorithm for finding node independent paths [2]_. More + formally, this algorithm is based on Whitney's theorem, which states + an inclusion relation among node connectivity, edge connectivity, and + minimum degree for any graph G. This theorem implies that every + `k`-component is nested inside a `k`-edge-component, which in turn, + is contained in a `k`-core. Thus, this algorithm computes node independent + paths among pairs of nodes in each biconnected part of each `k`-core, + and repeats this procedure for each `k` from 3 to the maximal core number + of a node in the input graph. + + Because, in practice, many nodes of the core of level `k` inside a + bicomponent actually are part of a component of level k, the auxiliary + graph needed for the algorithm is likely to be very dense. Thus, we use + a complement graph data structure (see `AntiGraph`) to save memory. + AntiGraph only stores information of the edges that are *not* present + in the actual auxiliary graph. When applying algorithms to this + complement graph data structure, it behaves as if it were the dense + version. + + See also + -------- + k_components + + References + ---------- + .. [1] Torrents, J. and F. Ferraro (2015) Structural Cohesion: + Visualization and Heuristics for Fast Computation. + https://arxiv.org/pdf/1503.04476v1 + + .. [2] White, Douglas R., and Mark Newman (2001) A Fast Algorithm for + Node-Independent Paths. Santa Fe Institute Working Paper #01-07-035 + https://www.santafe.edu/research/results/working-papers/fast-approximation-algorithms-for-finding-node-ind + + .. [3] Moody, J. and D. White (2003). Social cohesion and embeddedness: + A hierarchical conception of social groups. + American Sociological Review 68(1), 103--28. + https://doi.org/10.2307/3088904 + + """ + # Dictionary with connectivity level (k) as keys and a list of + # sets of nodes that form a k-component as values + k_components = defaultdict(list) + # make a few functions local for speed + node_connectivity = local_node_connectivity + k_core = nx.k_core + core_number = nx.core_number + biconnected_components = nx.biconnected_components + combinations = itertools.combinations + # Exact solution for k = {1,2} + # There is a linear time algorithm for triconnectivity, if we had an + # implementation available we could start from k = 4. + for component in nx.connected_components(G): + # isolated nodes have connectivity 0 + comp = set(component) + if len(comp) > 1: + k_components[1].append(comp) + for bicomponent in nx.biconnected_components(G): + # avoid considering dyads as bicomponents + bicomp = set(bicomponent) + if len(bicomp) > 2: + k_components[2].append(bicomp) + # There is no k-component of k > maximum core number + # \kappa(G) <= \lambda(G) <= \delta(G) + g_cnumber = core_number(G) + max_core = max(g_cnumber.values()) + for k in range(3, max_core + 1): + C = k_core(G, k, core_number=g_cnumber) + for nodes in biconnected_components(C): + # Build a subgraph SG induced by the nodes that are part of + # each biconnected component of the k-core subgraph C. + if len(nodes) < k: + continue + SG = G.subgraph(nodes) + # Build auxiliary graph + H = _AntiGraph() + H.add_nodes_from(SG.nodes()) + for u, v in combinations(SG, 2): + K = node_connectivity(SG, u, v, cutoff=k) + if k > K: + H.add_edge(u, v) + for h_nodes in biconnected_components(H): + if len(h_nodes) <= k: + continue + SH = H.subgraph(h_nodes) + for Gc in _cliques_heuristic(SG, SH, k, min_density): + for k_nodes in biconnected_components(Gc): + Gk = nx.k_core(SG.subgraph(k_nodes), k) + if len(Gk) <= k: + continue + k_components[k].append(set(Gk)) + return k_components + + +def _cliques_heuristic(G, H, k, min_density): + h_cnumber = nx.core_number(H) + for i, c_value in enumerate(sorted(set(h_cnumber.values()), reverse=True)): + cands = {n for n, c in h_cnumber.items() if c == c_value} + # Skip checking for overlap for the highest core value + if i == 0: + overlap = False + else: + overlap = set.intersection( + *[{x for x in H[n] if x not in cands} for n in cands] + ) + if overlap and len(overlap) < k: + SH = H.subgraph(cands | overlap) + else: + SH = H.subgraph(cands) + sh_cnumber = nx.core_number(SH) + SG = nx.k_core(G.subgraph(SH), k) + while not (_same(sh_cnumber) and nx.density(SH) >= min_density): + # This subgraph must be writable => .copy() + SH = H.subgraph(SG).copy() + if len(SH) <= k: + break + sh_cnumber = nx.core_number(SH) + sh_deg = dict(SH.degree()) + min_deg = min(sh_deg.values()) + SH.remove_nodes_from(n for n, d in sh_deg.items() if d == min_deg) + SG = nx.k_core(G.subgraph(SH), k) + else: + yield SG + + +def _same(measure, tol=0): + vals = set(measure.values()) + if (max(vals) - min(vals)) <= tol: + return True + return False + + +class _AntiGraph(nx.Graph): + """ + Class for complement graphs. + + The main goal is to be able to work with big and dense graphs with + a low memory footprint. + + In this class you add the edges that *do not exist* in the dense graph, + the report methods of the class return the neighbors, the edges and + the degree as if it was the dense graph. Thus it's possible to use + an instance of this class with some of NetworkX functions. In this + case we only use k-core, connected_components, and biconnected_components. + """ + + all_edge_dict = {"weight": 1} + + def single_edge_dict(self): + return self.all_edge_dict + + edge_attr_dict_factory = single_edge_dict # type: ignore[assignment] + + def __getitem__(self, n): + """Returns a dict of neighbors of node n in the dense graph. + + Parameters + ---------- + n : node + A node in the graph. + + Returns + ------- + adj_dict : dictionary + The adjacency dictionary for nodes connected to n. + + """ + all_edge_dict = self.all_edge_dict + return { + node: all_edge_dict for node in set(self._adj) - set(self._adj[n]) - {n} + } + + def neighbors(self, n): + """Returns an iterator over all neighbors of node n in the + dense graph. + """ + try: + return iter(set(self._adj) - set(self._adj[n]) - {n}) + except KeyError as err: + raise NetworkXError(f"The node {n} is not in the graph.") from err + + class AntiAtlasView(Mapping): + """An adjacency inner dict for AntiGraph""" + + def __init__(self, graph, node): + self._graph = graph + self._atlas = graph._adj[node] + self._node = node + + def __len__(self): + return len(self._graph) - len(self._atlas) - 1 + + def __iter__(self): + return (n for n in self._graph if n not in self._atlas and n != self._node) + + def __getitem__(self, nbr): + nbrs = set(self._graph._adj) - set(self._atlas) - {self._node} + if nbr in nbrs: + return self._graph.all_edge_dict + raise KeyError(nbr) + + class AntiAdjacencyView(AntiAtlasView): + """An adjacency outer dict for AntiGraph""" + + def __init__(self, graph): + self._graph = graph + self._atlas = graph._adj + + def __len__(self): + return len(self._atlas) + + def __iter__(self): + return iter(self._graph) + + def __getitem__(self, node): + if node not in self._graph: + raise KeyError(node) + return self._graph.AntiAtlasView(self._graph, node) + + @cached_property + def adj(self): + return self.AntiAdjacencyView(self) + + def subgraph(self, nodes): + """This subgraph method returns a full AntiGraph. Not a View""" + nodes = set(nodes) + G = _AntiGraph() + G.add_nodes_from(nodes) + for n in G: + Gnbrs = G.adjlist_inner_dict_factory() + G._adj[n] = Gnbrs + for nbr, d in self._adj[n].items(): + if nbr in G._adj: + Gnbrs[nbr] = d + G._adj[nbr][n] = d + G.graph = self.graph + return G + + class AntiDegreeView(nx.reportviews.DegreeView): + def __iter__(self): + all_nodes = set(self._succ) + for n in self._nodes: + nbrs = all_nodes - set(self._succ[n]) - {n} + yield (n, len(nbrs)) + + def __getitem__(self, n): + nbrs = set(self._succ) - set(self._succ[n]) - {n} + # AntiGraph is a ThinGraph so all edges have weight 1 + return len(nbrs) + (n in nbrs) + + @cached_property + def degree(self): + """Returns an iterator for (node, degree) and degree for single node. + + The node degree is the number of edges adjacent to the node. + + Parameters + ---------- + nbunch : iterable container, optional (default=all nodes) + A container of nodes. The container will be iterated + through once. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + deg: + Degree of the node, if a single node is passed as argument. + nd_iter : an iterator + The iterator returns two-tuples of (node, degree). + + See Also + -------- + degree + + Examples + -------- + >>> G = nx.path_graph(4) + >>> G.degree(0) # node 0 with degree 1 + 1 + >>> list(G.degree([0, 1])) + [(0, 1), (1, 2)] + + """ + return self.AntiDegreeView(self) + + def adjacency(self): + """Returns an iterator of (node, adjacency set) tuples for all nodes + in the dense graph. + + This is the fastest way to look at every edge. + For directed graphs, only outgoing adjacencies are included. + + Returns + ------- + adj_iter : iterator + An iterator of (node, adjacency set) for all nodes in + the graph. + + """ + for n in self._adj: + yield (n, set(self._adj) - set(self._adj[n]) - {n}) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/matching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/matching.py new file mode 100644 index 0000000000000000000000000000000000000000..dc0891947cb88c38930e9f8c5479d397e3aa923b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/matching.py @@ -0,0 +1,44 @@ +""" +************** +Graph Matching +************** + +Given a graph G = (V,E), a matching M in G is a set of pairwise non-adjacent +edges; that is, no two edges share a common vertex. + +`Wikipedia: Matching `_ +""" + +import networkx as nx + +__all__ = ["min_maximal_matching"] + + +@nx._dispatchable +def min_maximal_matching(G): + r"""Returns the minimum maximal matching of G. That is, out of all maximal + matchings of the graph G, the smallest is returned. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + min_maximal_matching : set + Returns a set of edges such that no two edges share a common endpoint + and every edge not in the set shares some common endpoint in the set. + Cardinality will be 2*OPT in the worst case. + + Notes + ----- + The algorithm computes an approximate solution for the minimum maximal + cardinality matching problem. The solution is no more than 2 * OPT in size. + Runtime is $O(|E|)$. + + References + ---------- + .. [1] Vazirani, Vijay Approximation Algorithms (2001) + """ + return nx.maximal_matching(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/maxcut.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/maxcut.py new file mode 100644 index 0000000000000000000000000000000000000000..f4e1da87c35ab821f4b3d0851bba19d599d8fa6a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/maxcut.py @@ -0,0 +1,143 @@ +import networkx as nx +from networkx.utils.decorators import not_implemented_for, py_random_state + +__all__ = ["randomized_partitioning", "one_exchange"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@py_random_state(1) +@nx._dispatchable(edge_attrs="weight") +def randomized_partitioning(G, seed=None, p=0.5, weight=None): + """Compute a random partitioning of the graph nodes and its cut value. + + A partitioning is calculated by observing each node + and deciding to add it to the partition with probability `p`, + returning a random cut and its corresponding value (the + sum of weights of edges connecting different partitions). + + Parameters + ---------- + G : NetworkX graph + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + p : scalar + Probability for each node to be part of the first partition. + Should be in [0,1] + + weight : object + Edge attribute key to use as weight. If not specified, edges + have weight one. + + Returns + ------- + cut_size : scalar + Value of the minimum cut. + + partition : pair of node sets + A partitioning of the nodes that defines a minimum cut. + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> cut_size, partition = nx.approximation.randomized_partitioning(G, seed=1) + >>> cut_size + 6 + >>> partition + ({0, 3, 4}, {1, 2}) + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + """ + cut = {node for node in G.nodes() if seed.random() < p} + cut_size = nx.algorithms.cut_size(G, cut, weight=weight) + partition = (cut, G.nodes - cut) + return cut_size, partition + + +def _swap_node_partition(cut, node): + return cut - {node} if node in cut else cut.union({node}) + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@py_random_state(2) +@nx._dispatchable(edge_attrs="weight") +def one_exchange(G, initial_cut=None, seed=None, weight=None): + """Compute a partitioning of the graphs nodes and the corresponding cut value. + + Use a greedy one exchange strategy to find a locally maximal cut + and its value, it works by finding the best node (one that gives + the highest gain to the cut value) to add to the current cut + and repeats this process until no improvement can be made. + + Parameters + ---------- + G : networkx Graph + Graph to find a maximum cut for. + + initial_cut : set + Cut to use as a starting point. If not supplied the algorithm + starts with an empty cut. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + weight : object + Edge attribute key to use as weight. If not specified, edges + have weight one. + + Returns + ------- + cut_value : scalar + Value of the maximum cut. + + partition : pair of node sets + A partitioning of the nodes that defines a maximum cut. + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> curr_cut_size, partition = nx.approximation.one_exchange(G, seed=1) + >>> curr_cut_size + 6 + >>> partition + ({0, 2}, {1, 3, 4}) + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + """ + if initial_cut is None: + initial_cut = set() + cut = set(initial_cut) + current_cut_size = nx.algorithms.cut_size(G, cut, weight=weight) + while True: + nodes = list(G.nodes()) + # Shuffling the nodes ensures random tie-breaks in the following call to max + seed.shuffle(nodes) + best_node_to_swap = max( + nodes, + key=lambda v: nx.algorithms.cut_size( + G, _swap_node_partition(cut, v), weight=weight + ), + default=None, + ) + potential_cut = _swap_node_partition(cut, best_node_to_swap) + potential_cut_size = nx.algorithms.cut_size(G, potential_cut, weight=weight) + + if potential_cut_size > current_cut_size: + cut = potential_cut + current_cut_size = potential_cut_size + else: + break + + partition = (cut, G.nodes - cut) + return current_cut_size, partition diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/ramsey.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/ramsey.py new file mode 100644 index 0000000000000000000000000000000000000000..0552e4a942c9c99fbf132300d7288595307052ff --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/ramsey.py @@ -0,0 +1,53 @@ +""" +Ramsey numbers. +""" + +import networkx as nx +from networkx.utils import not_implemented_for + +from ...utils import arbitrary_element + +__all__ = ["ramsey_R2"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def ramsey_R2(G): + r"""Compute the largest clique and largest independent set in `G`. + + This can be used to estimate bounds for the 2-color + Ramsey number `R(2;s,t)` for `G`. + + This is a recursive implementation which could run into trouble + for large recursions. Note that self-loop edges are ignored. + + Parameters + ---------- + G : NetworkX graph + Undirected graph + + Returns + ------- + max_pair : (set, set) tuple + Maximum clique, Maximum independent set. + + Raises + ------ + NetworkXNotImplemented + If the graph is directed or is a multigraph. + """ + if not G: + return set(), set() + + node = arbitrary_element(G) + nbrs = (nbr for nbr in nx.all_neighbors(G, node) if nbr != node) + nnbrs = nx.non_neighbors(G, node) + c_1, i_1 = ramsey_R2(G.subgraph(nbrs).copy()) + c_2, i_2 = ramsey_R2(G.subgraph(nnbrs).copy()) + + c_1.add(node) + i_2.add(node) + # Choose the larger of the two cliques and the larger of the two + # independent sets, according to cardinality. + return max(c_1, c_2, key=len), max(i_1, i_2, key=len) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/steinertree.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/steinertree.py new file mode 100644 index 0000000000000000000000000000000000000000..2f9632488fa306223910c58e1c760157e77ee6e0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/steinertree.py @@ -0,0 +1,265 @@ +from itertools import chain + +import networkx as nx +from networkx.utils import not_implemented_for, pairwise + +__all__ = ["metric_closure", "steiner_tree"] + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight", returns_graph=True) +def metric_closure(G, weight="weight"): + """Return the metric closure of a graph. + + The metric closure of a graph *G* is the complete graph in which each edge + is weighted by the shortest path distance between the nodes in *G* . + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + NetworkX graph + Metric closure of the graph `G`. + + Notes + ----- + .. deprecated:: 3.6 + `metric_closure` is deprecated and will be removed in NetworkX 3.8. + Use :func:`networkx.all_pairs_shortest_path_length` instead. + + """ + import warnings + + warnings.warn( + "metric_closure is deprecated and will be removed in NetworkX 3.8.\n" + "Use nx.all_pairs_shortest_path_length instead.", + category=DeprecationWarning, + stacklevel=5, + ) + + M = nx.Graph() + + Gnodes = set(G) + + # check for connected graph while processing first node + all_paths_iter = nx.all_pairs_dijkstra(G, weight=weight) + u, (distance, path) = next(all_paths_iter) + if len(G) != len(distance): + msg = "G is not a connected graph. metric_closure is not defined." + raise nx.NetworkXError(msg) + Gnodes.remove(u) + for v in Gnodes: + M.add_edge(u, v, distance=distance[v], path=path[v]) + + # first node done -- now process the rest + for u, (distance, path) in all_paths_iter: + Gnodes.remove(u) + for v in Gnodes: + M.add_edge(u, v, distance=distance[v], path=path[v]) + + return M + + +def _mehlhorn_steiner_tree(G, terminal_nodes, weight): + distances, paths = nx.multi_source_dijkstra(G, terminal_nodes, weight=weight) + + d_1 = {} + s = {} + for v in G.nodes(): + s[v] = paths[v][0] + d_1[(v, s[v])] = distances[v] + + # G1-G4 names match those from the Mehlhorn 1988 paper. + G_1_prime = nx.Graph() + # iterate over all edges to complete d1 + for u, v, data in G.edges(data=True): + su, sv = s[u], s[v] + weight_here = d_1[(u, su)] + data.get(weight, 1) + d_1[(v, sv)] + if not G_1_prime.has_edge(su, sv): + G_1_prime.add_edge(su, sv, weight_d1=weight_here) + else: + new_weight = min(weight_here, G_1_prime[su][sv]["weight_d1"]) + G_1_prime.add_edge(su, sv, weight_d1=new_weight) + + G_2 = nx.minimum_spanning_edges(G_1_prime, data=True, weight="weight_d1") + + G_3 = nx.Graph() + for u, v, _ in G_2: + path = nx.shortest_path(G, u, v, weight=weight) + for n1, n2 in pairwise(path): + G_3.add_edge(n1, n2, weight=G[n1][n2].get(weight, 1)) + + G_3_mst = list(nx.minimum_spanning_edges(G_3, data=False, weight=weight)) + if G.is_multigraph(): + G_3_mst = ( + (u, v, min(G[u][v], key=lambda k: G[u][v][k].get(weight, 1))) + for u, v in G_3_mst + ) + G_4 = G.edge_subgraph(G_3_mst).copy() + _remove_nonterminal_leaves(G_4, terminal_nodes) + return G_4.edges() + + +def _kou_steiner_tree(G, terminal_nodes, weight): + # Compute the metric closure only for terminal nodes + # Create a complete graph H from the metric edges + H = nx.Graph() + unvisited_terminals = set(terminal_nodes) + + # check for connected graph while processing first node + u = unvisited_terminals.pop() + distances, paths = nx.single_source_dijkstra(G, source=u, weight=weight) + if len(G) != len(distances): + msg = "G is not a connected graph." + raise nx.NetworkXError(msg) + for v in unvisited_terminals: + H.add_edge(u, v, distance=distances[v], path=paths[v]) + + # first node done -- now process the rest + for u in unvisited_terminals.copy(): + distances, paths = nx.single_source_dijkstra(G, source=u, weight=weight) + unvisited_terminals.remove(u) + for v in unvisited_terminals: + H.add_edge(u, v, distance=distances[v], path=paths[v]) + + # Use the 'distance' attribute of each edge provided by H. + mst_edges = nx.minimum_spanning_edges(H, weight="distance", data=True) + + # Create an iterator over each edge in each shortest path; repeats are okay + mst_all_edges = chain.from_iterable(pairwise(d["path"]) for u, v, d in mst_edges) + if G.is_multigraph(): + mst_all_edges = ( + (u, v, min(G[u][v], key=lambda k: G[u][v][k].get(weight, 1))) + for u, v in mst_all_edges + ) + + # Find the MST again, over this new set of edges + G_S = G.edge_subgraph(mst_all_edges) + T_S = nx.minimum_spanning_edges(G_S, weight="weight", data=False) + + # Leaf nodes that are not terminal might still remain; remove them here + T_H = G.edge_subgraph(T_S).copy() + _remove_nonterminal_leaves(T_H, terminal_nodes) + + return T_H.edges() + + +def _remove_nonterminal_leaves(G, terminals): + terminal_set = set(terminals) + leaves = {n for n in G if len(set(G[n]) - {n}) == 1} + nonterminal_leaves = leaves - terminal_set + + while nonterminal_leaves: + # Removing a node may create new non-terminal leaves, so we limit + # search for candidate non-terminal nodes to neighbors of current + # non-terminal nodes + candidate_leaves = set.union(*(set(G[n]) for n in nonterminal_leaves)) + candidate_leaves -= nonterminal_leaves | terminal_set + # Remove current set of non-terminal nodes + G.remove_nodes_from(nonterminal_leaves) + # Find any new non-terminal nodes from the set of candidates + leaves = {n for n in candidate_leaves if len(set(G[n]) - {n}) == 1} + nonterminal_leaves = leaves - terminal_set + + +ALGORITHMS = { + "kou": _kou_steiner_tree, + "mehlhorn": _mehlhorn_steiner_tree, +} + + +@not_implemented_for("directed") +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def steiner_tree(G, terminal_nodes, weight="weight", method=None): + r"""Return an approximation to the minimum Steiner tree of a graph. + + The minimum Steiner tree of `G` w.r.t a set of `terminal_nodes` (also *S*) + is a tree within `G` that spans those nodes and has minimum size (sum of + edge weights) among all such trees. + + The approximation algorithm is specified with the `method` keyword + argument. All three available algorithms produce a tree whose weight is + within a ``(2 - (2 / l))`` factor of the weight of the optimal Steiner tree, + where ``l`` is the minimum number of leaf nodes across all possible Steiner + trees. + + * ``"kou"`` [2]_ (runtime $O(|S| |V|^2)$) computes the minimum spanning tree of + the subgraph of the metric closure of *G* induced by the terminal nodes, + where the metric closure of *G* is the complete graph in which each edge is + weighted by the shortest path distance between the nodes in *G*. + + * ``"mehlhorn"`` [3]_ (runtime $O(|E|+|V|\log|V|)$) modifies Kou et al.'s + algorithm, beginning by finding the closest terminal node for each + non-terminal. This data is used to create a complete graph containing only + the terminal nodes, in which edge is weighted with the shortest path + distance between them. The algorithm then proceeds in the same way as Kou + et al.. + + Parameters + ---------- + G : NetworkX graph + + terminal_nodes : list + A list of terminal nodes for which minimum steiner tree is + to be found. + + weight : string (default = 'weight') + Use the edge attribute specified by this string as the edge weight. + Any edge attribute not present defaults to 1. + + method : string, optional (default = 'mehlhorn') + The algorithm to use to approximate the Steiner tree. + Supported options: 'kou', 'mehlhorn'. + Other inputs produce a ValueError. + + Returns + ------- + NetworkX graph + Approximation to the minimum steiner tree of `G` induced by + `terminal_nodes` . + + Raises + ------ + NetworkXNotImplemented + If `G` is directed. + + ValueError + If the specified `method` is not supported. + + Notes + ----- + For multigraphs, the edge between two nodes with minimum weight is the + edge put into the Steiner tree. + + + References + ---------- + .. [1] Steiner_tree_problem on Wikipedia. + https://en.wikipedia.org/wiki/Steiner_tree_problem + .. [2] Kou, L., G. Markowsky, and L. Berman. 1981. + ‘A Fast Algorithm for Steiner Trees’. + Acta Informatica 15 (2): 141–45. + https://doi.org/10.1007/BF00288961. + .. [3] Mehlhorn, Kurt. 1988. + ‘A Faster Approximation Algorithm for the Steiner Problem in Graphs’. + Information Processing Letters 27 (3): 125–28. + https://doi.org/10.1016/0020-0190(88)90066-X. + """ + if method is None: + method = "mehlhorn" + + try: + algo = ALGORITHMS[method] + except KeyError as e: + raise ValueError(f"{method} is not a valid choice for an algorithm.") from e + + edges = algo(G, terminal_nodes, weight) + # For multigraph we should add the minimal weight edge keys + if G.is_multigraph(): + edges = ( + (u, v, min(G[u][v], key=lambda k: G[u][v][k][weight])) for u, v in edges + ) + T = G.edge_subgraph(edges) + return T diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7050bc7df9479bc7a9917180f39992614ecf03b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_approx_clust_coeff.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_approx_clust_coeff.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b38fbfc060f27902803c862ddad79f04808ec55 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_approx_clust_coeff.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_clique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_clique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6606e7f08bc77e4019f1018d3965095a09c4f95 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_clique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78edfcc701b72665eb8924f1f022159c8bc041e2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_density.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_density.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..134ff30b82ffbd73baf6e0ef31f97471eefa5e87 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_density.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1da4fa8620bc6430f5f0dac8c35811c6c82951b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_dominating_set.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_dominating_set.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..363c67ade9fd4a341a0d1b738a4529b605297945 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_dominating_set.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc21b676d41e60941711e241d98c092e57e9b644 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f188d28ca7df9d05f9c385906cd073d180e248db Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_maxcut.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_maxcut.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ef80f5fe1efd62e74ad654cfebd282a932583b0b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_maxcut.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_ramsey.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_ramsey.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9aadbde23e4dd8c2b56e9d9d7215693c2e5f4b2a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_ramsey.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_steinertree.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_steinertree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9af90480910ca7ab898bcfa4f3dd654c5a809b9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_steinertree.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_traveling_salesman.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_traveling_salesman.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aebc821fab81c37c83908a021b45a3eb2aa85615 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_traveling_salesman.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_treewidth.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_treewidth.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1a76574c9579d676899009be576c61b2d074fcb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_treewidth.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_vertex_cover.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_vertex_cover.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93de5027ffa48e872f401b31b0688812b8409d1a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/__pycache__/test_vertex_cover.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py new file mode 100644 index 0000000000000000000000000000000000000000..5eab5c1ee79408c9f90a1993415a6c3d7d957141 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py @@ -0,0 +1,41 @@ +import networkx as nx +from networkx.algorithms.approximation import average_clustering + +# This approximation has to be exact in regular graphs +# with no triangles or with all possible triangles. + + +def test_petersen(): + # Actual coefficient is 0 + G = nx.petersen_graph() + assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G) + + +def test_petersen_seed(): + # Actual coefficient is 0 + G = nx.petersen_graph() + assert average_clustering(G, trials=len(G) // 2, seed=1) == nx.average_clustering(G) + + +def test_tetrahedral(): + # Actual coefficient is 1 + G = nx.tetrahedral_graph() + assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G) + + +def test_dodecahedral(): + # Actual coefficient is 0 + G = nx.dodecahedral_graph() + assert average_clustering(G, trials=len(G) // 2) == nx.average_clustering(G) + + +def test_empty(): + G = nx.empty_graph(5) + assert average_clustering(G, trials=len(G) // 2) == 0 + + +def test_complete(): + G = nx.complete_graph(5) + assert average_clustering(G, trials=len(G) // 2) == 1 + G = nx.complete_graph(7) + assert average_clustering(G, trials=len(G) // 2) == 1 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_clique.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_clique.py new file mode 100644 index 0000000000000000000000000000000000000000..b40dcb904063f2f1aca8811e73ec7d590beb5dc9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_clique.py @@ -0,0 +1,112 @@ +"""Unit tests for the :mod:`networkx.algorithms.approximation.clique` module.""" + +import networkx as nx +from networkx.algorithms.approximation import ( + clique_removal, + large_clique_size, + max_clique, + maximum_independent_set, +) + + +def is_independent_set(G, nodes): + """Returns True if and only if `nodes` is a clique in `G`. + + `G` is a NetworkX graph. `nodes` is an iterable of nodes in + `G`. + + """ + return G.subgraph(nodes).number_of_edges() == 0 + + +def is_clique(G, nodes): + """Returns True if and only if `nodes` is an independent set + in `G`. + + `G` is an undirected simple graph. `nodes` is an iterable of + nodes in `G`. + + """ + H = G.subgraph(nodes) + n = len(H) + return H.number_of_edges() == n * (n - 1) // 2 + + +class TestCliqueRemoval: + """Unit tests for the + :func:`~networkx.algorithms.approximation.clique_removal` function. + + """ + + def test_trivial_graph(self): + G = nx.trivial_graph() + independent_set, cliques = clique_removal(G) + assert is_independent_set(G, independent_set) + assert all(is_clique(G, clique) for clique in cliques) + # In fact, we should only have 1-cliques, that is, singleton nodes. + assert all(len(clique) == 1 for clique in cliques) + + def test_complete_graph(self): + G = nx.complete_graph(10) + independent_set, cliques = clique_removal(G) + assert is_independent_set(G, independent_set) + assert all(is_clique(G, clique) for clique in cliques) + + def test_barbell_graph(self): + G = nx.barbell_graph(10, 5) + independent_set, cliques = clique_removal(G) + assert is_independent_set(G, independent_set) + assert all(is_clique(G, clique) for clique in cliques) + + +class TestMaxClique: + """Unit tests for the :func:`networkx.algorithms.approximation.max_clique` + function. + + """ + + def test_null_graph(self): + G = nx.null_graph() + assert len(max_clique(G)) == 0 + + def test_complete_graph(self): + graph = nx.complete_graph(30) + # this should return the entire graph + mc = max_clique(graph) + assert 30 == len(mc) + + def test_maximal_by_cardinality(self): + """Tests that the maximal clique is computed according to maximum + cardinality of the sets. + + For more information, see pull request #1531. + + """ + G = nx.complete_graph(5) + G.add_edge(4, 5) + clique = max_clique(G) + assert len(clique) > 1 + + G = nx.lollipop_graph(30, 2) + clique = max_clique(G) + assert len(clique) > 2 + + +def test_large_clique_size(): + G = nx.complete_graph(9) + nx.add_cycle(G, [9, 10, 11]) + G.add_edge(8, 9) + G.add_edge(1, 12) + G.add_node(13) + + assert large_clique_size(G) == 9 + G.remove_node(5) + assert large_clique_size(G) == 8 + G.remove_edge(2, 3) + assert large_clique_size(G) == 7 + + +def test_independent_set(): + # smoke test + G = nx.Graph() + assert len(maximum_independent_set(G)) == 0 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_connectivity.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_connectivity.py new file mode 100644 index 0000000000000000000000000000000000000000..887db20bcaef8dd2641c64e963c789234aecbb20 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_connectivity.py @@ -0,0 +1,199 @@ +import pytest + +import networkx as nx +from networkx.algorithms import approximation as approx + + +def test_global_node_connectivity(): + # Figure 1 chapter on Connectivity + G = nx.Graph() + G.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 6), + (3, 4), + (3, 6), + (4, 6), + (4, 7), + (5, 7), + (6, 8), + (6, 9), + (7, 8), + (7, 10), + (8, 11), + (9, 10), + (9, 11), + (10, 11), + ] + ) + assert 2 == approx.local_node_connectivity(G, 1, 11) + assert 2 == approx.node_connectivity(G) + assert 2 == approx.node_connectivity(G, 1, 11) + + +def test_white_harary1(): + # Figure 1b white and harary (2001) + # A graph with high adhesion (edge connectivity) and low cohesion + # (node connectivity) + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.remove_node(7) + for i in range(4, 7): + G.add_edge(0, i) + G = nx.disjoint_union(G, nx.complete_graph(4)) + G.remove_node(G.order() - 1) + for i in range(7, 10): + G.add_edge(0, i) + assert 1 == approx.node_connectivity(G) + + +def test_complete_graphs(): + for n in range(5, 25, 5): + G = nx.complete_graph(n) + assert n - 1 == approx.node_connectivity(G) + assert n - 1 == approx.node_connectivity(G, 0, 3) + + +def test_empty_graphs(): + for k in range(5, 25, 5): + G = nx.empty_graph(k) + assert 0 == approx.node_connectivity(G) + assert 0 == approx.node_connectivity(G, 0, 3) + + +def test_petersen(): + G = nx.petersen_graph() + assert 3 == approx.node_connectivity(G) + assert 3 == approx.node_connectivity(G, 0, 5) + + +# Approximation fails with tutte graph +# def test_tutte(): +# G = nx.tutte_graph() +# assert_equal(3, approx.node_connectivity(G)) + + +def test_dodecahedral(): + G = nx.dodecahedral_graph() + assert 3 == approx.node_connectivity(G) + assert 3 == approx.node_connectivity(G, 0, 5) + + +def test_octahedral(): + G = nx.octahedral_graph() + assert 4 == approx.node_connectivity(G) + assert 4 == approx.node_connectivity(G, 0, 5) + + +# Approximation can fail with icosahedral graph depending +# on iteration order. +# def test_icosahedral(): +# G=nx.icosahedral_graph() +# assert_equal(5, approx.node_connectivity(G)) +# assert_equal(5, approx.node_connectivity(G, 0, 5)) + + +def test_only_source(): + G = nx.complete_graph(5) + pytest.raises(nx.NetworkXError, approx.node_connectivity, G, s=0) + + +def test_only_target(): + G = nx.complete_graph(5) + pytest.raises(nx.NetworkXError, approx.node_connectivity, G, t=0) + + +def test_missing_source(): + G = nx.path_graph(4) + pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 10, 1) + + +def test_missing_target(): + G = nx.path_graph(4) + pytest.raises(nx.NetworkXError, approx.node_connectivity, G, 1, 10) + + +def test_source_equals_target(): + G = nx.complete_graph(5) + pytest.raises(nx.NetworkXError, approx.local_node_connectivity, G, 0, 0) + + +def test_directed_node_connectivity(): + G = nx.cycle_graph(10, create_using=nx.DiGraph()) # only one direction + D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges + assert 1 == approx.node_connectivity(G) + assert 1 == approx.node_connectivity(G, 1, 4) + assert 2 == approx.node_connectivity(D) + assert 2 == approx.node_connectivity(D, 1, 4) + + +class TestAllPairsNodeConnectivityApprox: + @classmethod + def setup_class(cls): + cls.path = nx.path_graph(7) + cls.directed_path = nx.path_graph(7, create_using=nx.DiGraph()) + cls.cycle = nx.cycle_graph(7) + cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) + cls.gnp = nx.gnp_random_graph(30, 0.1) + cls.directed_gnp = nx.gnp_random_graph(30, 0.1, directed=True) + cls.K20 = nx.complete_graph(20) + cls.K10 = nx.complete_graph(10) + cls.K5 = nx.complete_graph(5) + cls.G_list = [ + cls.path, + cls.directed_path, + cls.cycle, + cls.directed_cycle, + cls.gnp, + cls.directed_gnp, + cls.K10, + cls.K5, + cls.K20, + ] + + def test_cycles(self): + K_undir = approx.all_pairs_node_connectivity(self.cycle) + for source in K_undir: + for target, k in K_undir[source].items(): + assert k == 2 + K_dir = approx.all_pairs_node_connectivity(self.directed_cycle) + for source in K_dir: + for target, k in K_dir[source].items(): + assert k == 1 + + def test_complete(self): + for G in [self.K10, self.K5, self.K20]: + K = approx.all_pairs_node_connectivity(G) + for source in K: + for target, k in K[source].items(): + assert k == len(G) - 1 + + def test_paths(self): + K_undir = approx.all_pairs_node_connectivity(self.path) + for source in K_undir: + for target, k in K_undir[source].items(): + assert k == 1 + K_dir = approx.all_pairs_node_connectivity(self.directed_path) + for source in K_dir: + for target, k in K_dir[source].items(): + if source < target: + assert k == 1 + else: + assert k == 0 + + def test_cutoff(self): + for G in [self.K10, self.K5, self.K20]: + for mp in [2, 3, 4]: + paths = approx.all_pairs_node_connectivity(G, cutoff=mp) + for source in paths: + for target, K in paths[source].items(): + assert K == mp + + def test_all_pairs_connectivity_nbunch(self): + G = nx.complete_graph(5) + nbunch = [0, 2, 3] + C = approx.all_pairs_node_connectivity(G, nbunch=nbunch) + assert len(C) == len(nbunch) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_density.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_density.py new file mode 100644 index 0000000000000000000000000000000000000000..32d634bcbe83dc089c2866dd310fa7d5ba0fe0d7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_density.py @@ -0,0 +1,146 @@ +import pytest + +import networkx as nx +import networkx.algorithms.approximation as approx + + +def close_cliques_example(d=12, D=300, h=24, k=2): + """ + Hard example from Harb, Elfarouk, Kent Quanrud, and Chandra Chekuri. + "Faster and scalable algorithms for densest subgraph and decomposition." + Advances in Neural Information Processing Systems 35 (2022): 26966-26979. + """ + Kh = nx.complete_graph(h) + KdD = nx.complete_bipartite_graph(d, D) + G = nx.disjoint_union_all([KdD] + [Kh for _ in range(k)]) + best_density = d * D / (d + D) # of the complete bipartite graph + return G, best_density, set(KdD.nodes) + + +@pytest.mark.parametrize("iterations", (1, 3)) +@pytest.mark.parametrize("n", range(4, 7)) +@pytest.mark.parametrize("method", ("greedy++", "fista")) +def test_star(n, iterations, method): + if method == "fista": + pytest.importorskip("numpy") + + G = nx.star_graph(n) + # The densest subgraph of a star network is the entire graph. + # The peeling algorithm would peel all the vertices with degree 1, + # and so should discover the densest subgraph in one iteration! + d, S = approx.densest_subgraph(G, iterations=iterations, method=method) + + assert d == pytest.approx(G.number_of_edges() / G.number_of_nodes()) + assert S == set(G) # The entire graph! + + +@pytest.mark.parametrize("method", ("greedy++", "fista")) +def test_greedy_plus_plus_complete_graph(method): + if method == "fista": + pytest.importorskip("numpy") + + G = nx.complete_graph(4) + # The density of a complete graph network is the entire graph: C(4, 2)/4 + # where C(n, 2) is n*(n-1)//2. The peeling algorithm would find + # the densest subgraph in one iteration! + d, S = approx.densest_subgraph(G, iterations=1, method=method) + + assert d == pytest.approx(6 / 4) # The density, 4/5=0.8. + assert S == {0, 1, 2, 3} # The entire graph! + + +def test_greedy_plus_plus_close_cliques(): + G, best_density, densest_set = close_cliques_example() + # NOTE: iterations=185 fails to ID the densest subgraph + greedy_pp, S_pp = approx.densest_subgraph(G, iterations=186, method="greedy++") + + assert greedy_pp == pytest.approx(best_density) + assert S_pp == densest_set + + +def test_fista_close_cliques(): + pytest.importorskip("numpy") + G, best_density, best_set = close_cliques_example() + # NOTE: iterations=12 fails to ID the densest subgraph + density, dense_set = approx.densest_subgraph(G, iterations=13, method="fista") + + assert density == pytest.approx(best_density) + assert dense_set == best_set + + +def bipartite_and_clique_example(d=5, D=200, k=2): + """ + Hard example from: Boob, Digvijay, Yu Gao, Richard Peng, Saurabh Sawlani, + Charalampos Tsourakakis, Di Wang, and Junxing Wang. "Flowless: Extracting + densest subgraphs without flow computations." In Proceedings of The Web + Conference 2020, pp. 573-583. 2020. + """ + B = nx.complete_bipartite_graph(d, D) + H = [nx.complete_graph(d + 2) for _ in range(k)] + G = nx.disjoint_union_all([B] + H) + + best_density = d * D / (d + D) # of the complete bipartite graph + correct_one_round_density = (2 * d * D + (d + 1) * (d + 2) * k) / ( + 2 * d + 2 * D + 2 * k * (d + 2) + ) + best_subgraph = set(B.nodes) + return G, best_density, best_subgraph, correct_one_round_density + + +def test_greedy_plus_plus_bipartite_and_clique(): + G, best_density, best_subgraph, correct_one_iter_density = ( + bipartite_and_clique_example() + ) + one_round_density, S_one = approx.densest_subgraph( + G, iterations=1, method="greedy++" + ) + assert one_round_density == pytest.approx(correct_one_iter_density) + assert S_one == set(G.nodes) + + ten_round_density, S_ten = approx.densest_subgraph( + G, iterations=10, method="greedy++" + ) + assert ten_round_density == pytest.approx(best_density) + assert S_ten == best_subgraph + + +def test_fista_bipartite_and_clique(): + pytest.importorskip("numpy") + G, best_density, best_subgraph, _ = bipartite_and_clique_example() + + ten_round_density, S_ten = approx.densest_subgraph(G, iterations=10, method="fista") + assert ten_round_density == pytest.approx(best_density) + assert S_ten == best_subgraph + + +def test_fista_big_dataset(): + pytest.importorskip("numpy") + G, best_density, best_subgraph = close_cliques_example(d=30, D=2000, h=60, k=20) + + # Note: iterations=12 fails to identify densest subgraph + density, dense_set = approx.densest_subgraph(G, iterations=13, method="fista") + + assert density == pytest.approx(best_density) + assert dense_set == best_subgraph + + +@pytest.mark.parametrize("iterations", (1, 3)) +def test_greedy_plus_plus_edgeless_cornercase(iterations): + G = nx.Graph() + assert approx.densest_subgraph(G, iterations=iterations, method="greedy++") == ( + 0, + set(), + ) + G.add_nodes_from(range(4)) + assert approx.densest_subgraph(G, iterations=iterations, method="greedy++") == ( + 0, + set(), + ) + + +@pytest.mark.parametrize("labels", ((1, 2, 3), ("a", "b", "c"))) +def test_gh_8271(labels): + """Test for graphs with nonstandard node labels.""" + pytest.importorskip("numpy") + G = nx.complete_graph(labels) + assert approx.densest_subgraph(G, method="fista") == (1, set(labels)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_distance_measures.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_distance_measures.py new file mode 100644 index 0000000000000000000000000000000000000000..3809a8fc667db5d95fb41074e7bab3ff551e1784 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_distance_measures.py @@ -0,0 +1,59 @@ +"""Unit tests for the :mod:`networkx.algorithms.approximation.distance_measures` module.""" + +import pytest + +import networkx as nx +from networkx.algorithms.approximation import diameter + + +class TestDiameter: + """Unit tests for the approximate diameter function + :func:`~networkx.algorithms.approximation.distance_measures.diameter`. + """ + + def test_null_graph(self): + """Test empty graph.""" + G = nx.null_graph() + with pytest.raises( + nx.NetworkXError, match="Expected non-empty NetworkX graph!" + ): + diameter(G) + + def test_undirected_non_connected(self): + """Test an undirected disconnected graph.""" + graph = nx.path_graph(10) + graph.remove_edge(3, 4) + with pytest.raises(nx.NetworkXError, match="Graph not connected."): + diameter(graph) + + def test_directed_non_strongly_connected(self): + """Test a directed non strongly connected graph.""" + graph = nx.path_graph(10, create_using=nx.DiGraph()) + with pytest.raises(nx.NetworkXError, match="DiGraph not strongly connected."): + diameter(graph) + + def test_complete_undirected_graph(self): + """Test a complete undirected graph.""" + graph = nx.complete_graph(10) + assert diameter(graph) == 1 + + def test_complete_directed_graph(self): + """Test a complete directed graph.""" + graph = nx.complete_graph(10, create_using=nx.DiGraph()) + assert diameter(graph) == 1 + + def test_undirected_path_graph(self): + """Test an undirected path graph with 10 nodes.""" + graph = nx.path_graph(10) + assert diameter(graph) == 9 + + def test_directed_path_graph(self): + """Test a directed path graph with 10 nodes.""" + graph = nx.path_graph(10).to_directed() + assert diameter(graph) == 9 + + def test_single_node(self): + """Test a graph which contains just a node.""" + graph = nx.Graph() + graph.add_node(1) + assert diameter(graph) == 0 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_dominating_set.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_dominating_set.py new file mode 100644 index 0000000000000000000000000000000000000000..6b90d85ecf73bb56370fd92fdec25e3bbbb91ce3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_dominating_set.py @@ -0,0 +1,78 @@ +import pytest + +import networkx as nx +from networkx.algorithms.approximation import ( + min_edge_dominating_set, + min_weighted_dominating_set, +) + + +class TestMinWeightDominatingSet: + def test_min_weighted_dominating_set(self): + graph = nx.Graph() + graph.add_edge(1, 2) + graph.add_edge(1, 5) + graph.add_edge(2, 3) + graph.add_edge(2, 5) + graph.add_edge(3, 4) + graph.add_edge(3, 6) + graph.add_edge(5, 6) + + vertices = {1, 2, 3, 4, 5, 6} + # due to ties, this might be hard to test tight bounds + dom_set = min_weighted_dominating_set(graph) + for vertex in vertices - dom_set: + neighbors = set(graph.neighbors(vertex)) + assert len(neighbors & dom_set) > 0, "Non dominating set found!" + + def test_star_graph(self): + """Tests that an approximate dominating set for the star graph, + even when the center node does not have the smallest integer + label, gives just the center node. + + For more information, see #1527. + + """ + # Create a star graph in which the center node has the highest + # label instead of the lowest. + G = nx.star_graph(10) + G = nx.relabel_nodes(G, {0: 9, 9: 0}) + assert min_weighted_dominating_set(G) == {9} + + def test_null_graph(self): + """Tests that the unique dominating set for the null graph is an empty set""" + G = nx.Graph() + assert min_weighted_dominating_set(G) == set() + + def test_min_edge_dominating_set(self): + graph = nx.path_graph(5) + dom_set = min_edge_dominating_set(graph) + + # this is a crappy way to test, but good enough for now. + for edge in graph.edges(): + if edge in dom_set: + continue + else: + u, v = edge + found = False + for dom_edge in dom_set: + found |= u == dom_edge[0] or u == dom_edge[1] + assert found, "Non adjacent edge found!" + + graph = nx.complete_graph(10) + dom_set = min_edge_dominating_set(graph) + + # this is a crappy way to test, but good enough for now. + for edge in graph.edges(): + if edge in dom_set: + continue + else: + u, v = edge + found = False + for dom_edge in dom_set: + found |= u == dom_edge[0] or u == dom_edge[1] + assert found, "Non adjacent edge found!" + + graph = nx.Graph() # empty Networkx graph + with pytest.raises(ValueError, match="Expected non-empty NetworkX graph!"): + min_edge_dominating_set(graph) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_kcomponents.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_kcomponents.py new file mode 100644 index 0000000000000000000000000000000000000000..65ba802171a6b43a5157f12010c8164e5e867eb8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_kcomponents.py @@ -0,0 +1,303 @@ +# Test for approximation to k-components algorithm +import pytest + +import networkx as nx +from networkx.algorithms.approximation import k_components +from networkx.algorithms.approximation.kcomponents import _AntiGraph, _same + + +def build_k_number_dict(k_components): + k_num = {} + for k, comps in sorted(k_components.items()): + for comp in comps: + for node in comp: + k_num[node] = k + return k_num + + +## +# Some nice synthetic graphs +## + + +def graph_example_1(): + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") + labels = {v: k for k, v in rlabels.items()} + + for nodes in [ + (labels[(0, 0)], labels[(1, 0)]), + (labels[(0, 4)], labels[(1, 4)]), + (labels[(3, 0)], labels[(4, 0)]), + (labels[(3, 4)], labels[(4, 4)]), + ]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing a node + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + G.add_edge(new_node + 16, new_node + 5) + return G + + +def torrents_and_ferraro_graph(): + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") + labels = {v: k for k, v in rlabels.items()} + + for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing a node + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + # Commenting this makes the graph not biconnected !! + # This stupid mistake make one reviewer very angry :P + G.add_edge(new_node + 16, new_node + 8) + + for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing two nodes + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + nbrs2 = G[new_node + 9] + G.remove_node(new_node + 9) + for nbr in nbrs2: + G.add_edge(new_node + 18, nbr) + return G + + +# Helper function + + +def _check_connectivity(G): + result = k_components(G) + for k, components in result.items(): + if k < 3: + continue + for component in components: + C = G.subgraph(component) + K = nx.node_connectivity(C) + assert K >= k + + +def test_torrents_and_ferraro_graph(): + G = torrents_and_ferraro_graph() + _check_connectivity(G) + + +def test_example_1(): + G = graph_example_1() + _check_connectivity(G) + + +def test_karate_0(): + G = nx.karate_club_graph() + _check_connectivity(G) + + +def test_karate_1(): + karate_k_num = { + 0: 4, + 1: 4, + 2: 4, + 3: 4, + 4: 3, + 5: 3, + 6: 3, + 7: 4, + 8: 4, + 9: 2, + 10: 3, + 11: 1, + 12: 2, + 13: 4, + 14: 2, + 15: 2, + 16: 2, + 17: 2, + 18: 2, + 19: 3, + 20: 2, + 21: 2, + 22: 2, + 23: 3, + 24: 3, + 25: 3, + 26: 2, + 27: 3, + 28: 3, + 29: 3, + 30: 4, + 31: 3, + 32: 4, + 33: 4, + } + approx_karate_k_num = karate_k_num.copy() + approx_karate_k_num[24] = 2 + approx_karate_k_num[25] = 2 + G = nx.karate_club_graph() + k_comps = k_components(G) + k_num = build_k_number_dict(k_comps) + assert k_num in (karate_k_num, approx_karate_k_num) + + +def test_example_1_detail_3_and_4(): + G = graph_example_1() + result = k_components(G) + # In this example graph there are 8 3-components, 4 with 15 nodes + # and 4 with 5 nodes. + assert len(result[3]) == 8 + assert len([c for c in result[3] if len(c) == 15]) == 4 + assert len([c for c in result[3] if len(c) == 5]) == 4 + # There are also 8 4-components all with 5 nodes. + assert len(result[4]) == 8 + assert all(len(c) == 5 for c in result[4]) + # Finally check that the k-components detected have actually node + # connectivity >= k. + for k, components in result.items(): + if k < 3: + continue + for component in components: + K = nx.node_connectivity(G.subgraph(component)) + assert K >= k + + +def test_directed(): + with pytest.raises(nx.NetworkXNotImplemented): + G = nx.gnp_random_graph(10, 0.4, directed=True) + kc = k_components(G) + + +def test_same(): + equal = {"A": 2, "B": 2, "C": 2} + slightly_different = {"A": 2, "B": 1, "C": 2} + different = {"A": 2, "B": 8, "C": 18} + assert _same(equal) + assert not _same(slightly_different) + assert _same(slightly_different, tol=1) + assert not _same(different) + assert not _same(different, tol=4) + + +class TestAntiGraph: + @classmethod + def setup_class(cls): + cls.Gnp = nx.gnp_random_graph(20, 0.8, seed=42) + cls.Anp = _AntiGraph(nx.complement(cls.Gnp)) + cls.Gd = nx.davis_southern_women_graph() + cls.Ad = _AntiGraph(nx.complement(cls.Gd)) + cls.Gk = nx.karate_club_graph() + cls.Ak = _AntiGraph(nx.complement(cls.Gk)) + cls.GA = [(cls.Gnp, cls.Anp), (cls.Gd, cls.Ad), (cls.Gk, cls.Ak)] + + def test_size(self): + for G, A in self.GA: + n = G.order() + s = len(list(G.edges())) + len(list(A.edges())) + assert s == (n * (n - 1)) / 2 + + def test_degree(self): + for G, A in self.GA: + assert sorted(G.degree()) == sorted(A.degree()) + + def test_core_number(self): + for G, A in self.GA: + assert nx.core_number(G) == nx.core_number(A) + + def test_connected_components(self): + # ccs are same unless isolated nodes or any node has degree=len(G)-1 + # graphs in self.GA avoid this problem + for G, A in self.GA: + gc = [set(c) for c in nx.connected_components(G)] + ac = [set(c) for c in nx.connected_components(A)] + for comp in ac: + assert comp in gc + + def test_adj(self): + for G, A in self.GA: + for n, nbrs in G.adj.items(): + a_adj = sorted((n, sorted(ad)) for n, ad in A.adj.items()) + g_adj = sorted((n, sorted(ad)) for n, ad in G.adj.items()) + assert a_adj == g_adj + + def test_adjacency(self): + for G, A in self.GA: + a_adj = list(A.adjacency()) + for n, nbrs in G.adjacency(): + assert (n, set(nbrs)) in a_adj + + def test_neighbors(self): + for G, A in self.GA: + node = list(G.nodes())[0] + assert set(G.neighbors(node)) == set(A.neighbors(node)) + + def test_node_not_in_graph(self): + for G, A in self.GA: + node = "non_existent_node" + pytest.raises(nx.NetworkXError, A.neighbors, node) + pytest.raises(nx.NetworkXError, G.neighbors, node) + + def test_degree_thingraph(self): + for G, A in self.GA: + node = list(G.nodes())[0] + nodes = list(G.nodes())[1:4] + assert G.degree(node) == A.degree(node) + assert sum(d for n, d in G.degree()) == sum(d for n, d in A.degree()) + # AntiGraph is a ThinGraph, so all the weights are 1 + assert sum(d for n, d in A.degree()) == sum( + d for n, d in A.degree(weight="weight") + ) + assert sum(d for n, d in G.degree(nodes)) == sum( + d for n, d in A.degree(nodes) + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_matching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_matching.py new file mode 100644 index 0000000000000000000000000000000000000000..f50da3d2e07310fc19e1db2bd18fdce23223771c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_matching.py @@ -0,0 +1,8 @@ +import networkx as nx +import networkx.algorithms.approximation as a + + +def test_min_maximal_matching(): + # smoke test + G = nx.Graph() + assert len(a.min_maximal_matching(G)) == 0 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py new file mode 100644 index 0000000000000000000000000000000000000000..ef0424401e4b2a7e6d580c762f23cea240e55b3c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_maxcut.py @@ -0,0 +1,94 @@ +import random + +import pytest + +import networkx as nx +from networkx.algorithms.approximation import maxcut + + +@pytest.mark.parametrize( + "f", (nx.approximation.randomized_partitioning, nx.approximation.one_exchange) +) +@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph)) +def test_raises_on_directed_and_multigraphs(f, graph_constructor): + G = graph_constructor([(0, 1), (1, 2)]) + with pytest.raises(nx.NetworkXNotImplemented): + f(G) + + +def _is_valid_cut(G, set1, set2): + union = set1.union(set2) + assert union == set(G.nodes) + assert len(set1) + len(set2) == G.number_of_nodes() + + +def _cut_is_locally_optimal(G, cut_size, set1): + # test if cut can be locally improved + for i, node in enumerate(set1): + cut_size_without_node = nx.algorithms.cut_size( + G, set1 - {node}, weight="weight" + ) + assert cut_size_without_node <= cut_size + + +def test_random_partitioning(): + G = nx.complete_graph(5) + _, (set1, set2) = maxcut.randomized_partitioning(G, seed=5) + _is_valid_cut(G, set1, set2) + + +def test_random_partitioning_all_to_one(): + G = nx.complete_graph(5) + _, (set1, set2) = maxcut.randomized_partitioning(G, p=1) + _is_valid_cut(G, set1, set2) + assert len(set1) == G.number_of_nodes() + assert len(set2) == 0 + + +def test_one_exchange_basic(): + G = nx.complete_graph(5) + random.seed(5) + for u, v, w in G.edges(data=True): + w["weight"] = random.randrange(-100, 100, 1) / 10 + + initial_cut = set(random.sample(sorted(G.nodes()), k=5)) + cut_size, (set1, set2) = maxcut.one_exchange( + G, initial_cut, weight="weight", seed=5 + ) + + _is_valid_cut(G, set1, set2) + _cut_is_locally_optimal(G, cut_size, set1) + + +def test_one_exchange_optimal(): + # Greedy one exchange should find the optimal solution for this graph (14) + G = nx.Graph() + G.add_edge(1, 2, weight=3) + G.add_edge(1, 3, weight=3) + G.add_edge(1, 4, weight=3) + G.add_edge(1, 5, weight=3) + G.add_edge(2, 3, weight=5) + + cut_size, (set1, set2) = maxcut.one_exchange(G, weight="weight", seed=5) + + _is_valid_cut(G, set1, set2) + _cut_is_locally_optimal(G, cut_size, set1) + # check global optimality + assert cut_size == 14 + + +def test_negative_weights(): + G = nx.complete_graph(5) + random.seed(5) + for u, v, w in G.edges(data=True): + w["weight"] = -1 * random.random() + + initial_cut = set(random.sample(sorted(G.nodes()), k=5)) + cut_size, (set1, set2) = maxcut.one_exchange(G, initial_cut, weight="weight") + + # make sure it is a valid cut + _is_valid_cut(G, set1, set2) + # check local optimality + _cut_is_locally_optimal(G, cut_size, set1) + # test that all nodes are in the same partition + assert len(set1) == len(G.nodes) or len(set2) == len(G.nodes) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_ramsey.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_ramsey.py new file mode 100644 index 0000000000000000000000000000000000000000..32fe1fb8fa917c557954d9da0d960895a6953a11 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_ramsey.py @@ -0,0 +1,31 @@ +import networkx as nx +import networkx.algorithms.approximation as apxa + + +def test_ramsey(): + # this should only find the complete graph + graph = nx.complete_graph(10) + c, i = apxa.ramsey_R2(graph) + cdens = nx.density(graph.subgraph(c)) + assert cdens == 1.0, "clique not correctly found by ramsey!" + idens = nx.density(graph.subgraph(i)) + assert idens == 0.0, "i-set not correctly found by ramsey!" + + # this trivial graph has no cliques. should just find i-sets + graph = nx.trivial_graph() + c, i = apxa.ramsey_R2(graph) + assert c == {0}, "clique not correctly found by ramsey!" + assert i == {0}, "i-set not correctly found by ramsey!" + + graph = nx.barbell_graph(10, 5, nx.Graph()) + c, i = apxa.ramsey_R2(graph) + cdens = nx.density(graph.subgraph(c)) + assert cdens == 1.0, "clique not correctly found by ramsey!" + idens = nx.density(graph.subgraph(i)) + assert idens == 0.0, "i-set not correctly found by ramsey!" + + # add self-loops and test again + graph.add_edges_from([(n, n) for n in range(0, len(graph), 2)]) + cc, ii = apxa.ramsey_R2(graph) + assert cc == c + assert ii == i diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_steinertree.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_steinertree.py new file mode 100644 index 0000000000000000000000000000000000000000..28074cac8704ed47d063f4ca6b57f1411e9e270e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_steinertree.py @@ -0,0 +1,306 @@ +import pytest + +import networkx as nx +from networkx.algorithms.approximation.steinertree import ( + _remove_nonterminal_leaves, + metric_closure, + steiner_tree, +) +from networkx.utils import edges_equal + + +@pytest.fixture(params=["kou", "mehlhorn"]) +def method(request): + return request.param + + +class TestSteinerTree: + @classmethod + def setup_class(cls): + G1 = nx.Graph() + G1.add_edge(1, 2, weight=10) + G1.add_edge(2, 3, weight=10) + G1.add_edge(3, 4, weight=10) + G1.add_edge(4, 5, weight=10) + G1.add_edge(5, 6, weight=10) + G1.add_edge(2, 7, weight=1) + G1.add_edge(7, 5, weight=1) + + G2 = nx.Graph() + G2.add_edge(0, 5, weight=6) + G2.add_edge(1, 2, weight=2) + G2.add_edge(1, 5, weight=3) + G2.add_edge(2, 4, weight=4) + G2.add_edge(3, 5, weight=5) + G2.add_edge(4, 5, weight=1) + + G3 = nx.Graph() + G3.add_edge(1, 2, weight=8) + G3.add_edge(1, 9, weight=3) + G3.add_edge(1, 8, weight=6) + G3.add_edge(1, 10, weight=2) + G3.add_edge(1, 14, weight=3) + G3.add_edge(2, 3, weight=6) + G3.add_edge(3, 4, weight=3) + G3.add_edge(3, 10, weight=2) + G3.add_edge(3, 11, weight=1) + G3.add_edge(4, 5, weight=1) + G3.add_edge(4, 11, weight=1) + G3.add_edge(5, 6, weight=4) + G3.add_edge(5, 11, weight=2) + G3.add_edge(5, 12, weight=1) + G3.add_edge(5, 13, weight=3) + G3.add_edge(6, 7, weight=2) + G3.add_edge(6, 12, weight=3) + G3.add_edge(6, 13, weight=1) + G3.add_edge(7, 8, weight=3) + G3.add_edge(7, 9, weight=3) + G3.add_edge(7, 11, weight=5) + G3.add_edge(7, 13, weight=2) + G3.add_edge(7, 14, weight=4) + G3.add_edge(8, 9, weight=2) + G3.add_edge(9, 14, weight=1) + G3.add_edge(10, 11, weight=2) + G3.add_edge(10, 14, weight=1) + G3.add_edge(11, 12, weight=1) + G3.add_edge(11, 14, weight=7) + G3.add_edge(12, 14, weight=3) + G3.add_edge(12, 15, weight=1) + G3.add_edge(13, 14, weight=4) + G3.add_edge(13, 15, weight=1) + G3.add_edge(14, 15, weight=2) + + G4 = nx.Graph() + G4.add_edge(0, 2, my_weight=2) + G4.add_edge(0, 1, my_weight=0.1) + G4.add_edge(1, 2, my_weight=0.1) + G4.add_edge(2, 3, my_weight=1) + G4.add_edge(2, 4) + + cls.G1 = G1 + cls.G2 = G2 + cls.G3 = G3 + cls.G4 = G4 + + cls.G1_term_nodes = [1, 2, 3, 4, 5] + cls.G2_term_nodes = [0, 2, 3] + cls.G3_term_nodes = [1, 3, 5, 6, 8, 10, 11, 12, 13] + cls.G4_term_nodes = [0, 3, 4] + + def test_connected_metric_closure(self): + G = self.G1.copy() + G.add_node(100) + with pytest.raises(nx.NetworkXError): + with pytest.deprecated_call(): + metric_closure(G) + + def test_metric_closure(self): + with pytest.deprecated_call(): + M = metric_closure(self.G1) + mc = [ + (1, 2, {"distance": 10, "path": [1, 2]}), + (1, 3, {"distance": 20, "path": [1, 2, 3]}), + (1, 4, {"distance": 22, "path": [1, 2, 7, 5, 4]}), + (1, 5, {"distance": 12, "path": [1, 2, 7, 5]}), + (1, 6, {"distance": 22, "path": [1, 2, 7, 5, 6]}), + (1, 7, {"distance": 11, "path": [1, 2, 7]}), + (2, 3, {"distance": 10, "path": [2, 3]}), + (2, 4, {"distance": 12, "path": [2, 7, 5, 4]}), + (2, 5, {"distance": 2, "path": [2, 7, 5]}), + (2, 6, {"distance": 12, "path": [2, 7, 5, 6]}), + (2, 7, {"distance": 1, "path": [2, 7]}), + (3, 4, {"distance": 10, "path": [3, 4]}), + (3, 5, {"distance": 12, "path": [3, 2, 7, 5]}), + (3, 6, {"distance": 22, "path": [3, 2, 7, 5, 6]}), + (3, 7, {"distance": 11, "path": [3, 2, 7]}), + (4, 5, {"distance": 10, "path": [4, 5]}), + (4, 6, {"distance": 20, "path": [4, 5, 6]}), + (4, 7, {"distance": 11, "path": [4, 5, 7]}), + (5, 6, {"distance": 10, "path": [5, 6]}), + (5, 7, {"distance": 1, "path": [5, 7]}), + (6, 7, {"distance": 11, "path": [6, 5, 7]}), + ] + assert edges_equal(list(M.edges(data=True)), mc) + + def test_steiner_tree(self, method): + valid_steiner_trees = [ + [ + [ + (1, 2, {"weight": 10}), + (2, 3, {"weight": 10}), + (2, 7, {"weight": 1}), + (3, 4, {"weight": 10}), + (5, 7, {"weight": 1}), + ], + [ + (1, 2, {"weight": 10}), + (2, 7, {"weight": 1}), + (3, 4, {"weight": 10}), + (4, 5, {"weight": 10}), + (5, 7, {"weight": 1}), + ], + [ + (1, 2, {"weight": 10}), + (2, 3, {"weight": 10}), + (2, 7, {"weight": 1}), + (4, 5, {"weight": 10}), + (5, 7, {"weight": 1}), + ], + ], + [ + [ + (0, 5, {"weight": 6}), + (1, 2, {"weight": 2}), + (1, 5, {"weight": 3}), + (3, 5, {"weight": 5}), + ], + [ + (0, 5, {"weight": 6}), + (4, 2, {"weight": 4}), + (4, 5, {"weight": 1}), + (3, 5, {"weight": 5}), + ], + ], + [ + [ + (1, 10, {"weight": 2}), + (3, 10, {"weight": 2}), + (3, 11, {"weight": 1}), + (5, 12, {"weight": 1}), + (6, 13, {"weight": 1}), + (8, 9, {"weight": 2}), + (9, 14, {"weight": 1}), + (10, 14, {"weight": 1}), + (11, 12, {"weight": 1}), + (12, 15, {"weight": 1}), + (13, 15, {"weight": 1}), + ] + ], + ] + for G, term_nodes, valid_trees in zip( + [self.G1, self.G2, self.G3], + [self.G1_term_nodes, self.G2_term_nodes, self.G3_term_nodes], + valid_steiner_trees, + ): + S = steiner_tree(G, term_nodes, method=method) + assert any( + edges_equal(list(S.edges(data=True)), valid_tree) + for valid_tree in valid_trees + ) + + def test_multigraph_steiner_tree(self, method): + G = nx.MultiGraph() + G.add_edges_from( + [ + (1, 2, 0, {"weight": 1}), + (2, 3, 0, {"weight": 999}), + (2, 3, 1, {"weight": 1}), + (3, 4, 0, {"weight": 1}), + (3, 5, 0, {"weight": 1}), + ] + ) + terminal_nodes = [2, 4, 5] + expected_edges = [ + (2, 3, 1, {"weight": 1}), # edge with key 1 has lower weight + (3, 4, 0, {"weight": 1}), + (3, 5, 0, {"weight": 1}), + ] + S = steiner_tree(G, terminal_nodes, method=method) + assert edges_equal(S.edges(data=True, keys=True), expected_edges) + + def test_remove_nonterminal_leaves(self): + G = nx.path_graph(10) + _remove_nonterminal_leaves(G, [4, 5, 6]) + + assert list(G) == [4, 5, 6] # only the terminal nodes are left + + @pytest.mark.parametrize( + ("weight", "expected_edges"), + [ + ( + None, + [ + (0, 2, {"my_weight": 2}), + (2, 3, {"my_weight": 1}), + (2, 4, {}), + ], + ), + ( + "my_weight", + [ + (0, 1, {"my_weight": 0.1}), + (1, 2, {"my_weight": 0.1}), + (2, 3, {"my_weight": 1}), + (2, 4, {}), + ], + ), + ], + ) + def test_weighted(self, method, weight, expected_edges): + G = self.G4 + terminal_nodes = self.G4_term_nodes + + S = steiner_tree(G, terminal_nodes, method=method, weight=weight) + assert edges_equal(list(S.edges(data=True)), expected_edges) + + +def test_steiner_tree_weight_attribute(method): + G = nx.star_graph(4) + # Add an edge attribute that is named something other than "weight" + nx.set_edge_attributes(G, {e: 10 for e in G.edges}, name="distance") + H = nx.approximation.steiner_tree(G, [1, 3], method=method, weight="distance") + assert nx.utils.edges_equal(H.edges, [(0, 1), (0, 3)]) + + +def test_steiner_tree_multigraph_weight_attribute(method): + G = nx.cycle_graph(3, create_using=nx.MultiGraph) + nx.set_edge_attributes(G, {e: 10 for e in G.edges}, name="distance") + G.add_edge(2, 0, distance=5) + H = nx.approximation.steiner_tree(G, list(G), method=method, weight="distance") + assert len(H.edges) == 2 and H.has_edge(2, 0, key=1) + assert sum(dist for *_, dist in H.edges(data="distance")) == 15 + + +@pytest.mark.parametrize("method", (None, "mehlhorn", "kou")) +def test_steiner_tree_methods(method): + G = nx.star_graph(4) + expected = nx.Graph([(0, 1), (0, 3)]) + st = nx.approximation.steiner_tree(G, [1, 3], method=method) + assert nx.utils.edges_equal(st.edges, expected.edges) + + +def test_steiner_tree_method_invalid(): + G = nx.star_graph(4) + with pytest.raises( + ValueError, match="invalid_method is not a valid choice for an algorithm." + ): + nx.approximation.steiner_tree(G, terminal_nodes=[1, 3], method="invalid_method") + + +def test_steiner_tree_remove_non_terminal_leaves_self_loop_edges(): + # To verify that the last step of the steiner tree approximation + # behaves in the case where a non-terminal leaf has a self loop edge + G = nx.path_graph(10) + + # Add self loops to the terminal nodes + G.add_edges_from([(2, 2), (3, 3), (4, 4), (7, 7), (8, 8)]) + + # Remove non-terminal leaves + _remove_nonterminal_leaves(G, [4, 5, 6, 7]) + + # The terminal nodes should be left + assert list(G) == [4, 5, 6, 7] # only the terminal nodes are left + + +def test_steiner_tree_non_terminal_leaves_multigraph_self_loop_edges(): + # To verify that the last step of the steiner tree approximation + # behaves in the case where a non-terminal leaf has a self loop edge + G = nx.MultiGraph() + G.add_edges_from([(i, i + 1) for i in range(10)]) + G.add_edges_from([(2, 2), (3, 3), (4, 4), (4, 4), (7, 7)]) + + # Remove non-terminal leaves + _remove_nonterminal_leaves(G, [4, 5, 6, 7]) + + # Only the terminal nodes should be left + assert list(G) == [4, 5, 6, 7] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_traveling_salesman.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_traveling_salesman.py new file mode 100644 index 0000000000000000000000000000000000000000..7f5c5ab584be5389b2c4e1a56661cb48fca7c459 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_traveling_salesman.py @@ -0,0 +1,1014 @@ +"""Unit tests for the traveling_salesman module.""" + +import random + +import pytest + +import networkx as nx +import networkx.algorithms.approximation as nx_app + +pairwise = nx.utils.pairwise + + +def test_christofides_hamiltonian(): + random.seed(42) + G = nx.complete_graph(20) + for u, v in G.edges(): + G[u][v]["weight"] = random.randint(0, 10) + + H = nx.Graph() + H.add_edges_from(pairwise(nx_app.christofides(G))) + H.remove_edges_from(nx.find_cycle(H)) + assert len(H.edges) == 0 + + tree = nx.minimum_spanning_tree(G, weight="weight") + H = nx.Graph() + H.add_edges_from(pairwise(nx_app.christofides(G, tree))) + H.remove_edges_from(nx.find_cycle(H)) + assert len(H.edges) == 0 + + +def test_christofides_incomplete_graph(): + G = nx.complete_graph(10) + G.remove_edge(0, 1) + pytest.raises(nx.NetworkXError, nx_app.christofides, G) + + +def test_christofides_ignore_selfloops(): + G = nx.complete_graph(5) + G.add_edge(3, 3) + cycle = nx_app.christofides(G) + assert len(cycle) - 1 == len(G) == len(set(cycle)) + + +# set up graphs for other tests +class TestBase: + @classmethod + def setup_class(cls): + cls.DG = nx.DiGraph() + cls.DG.add_weighted_edges_from( + { + ("A", "B", 3), + ("A", "C", 17), + ("A", "D", 14), + ("B", "A", 3), + ("B", "C", 12), + ("B", "D", 16), + ("C", "A", 13), + ("C", "B", 12), + ("C", "D", 4), + ("D", "A", 14), + ("D", "B", 15), + ("D", "C", 2), + } + ) + cls.DG_cycle = ["D", "C", "B", "A", "D"] + cls.DG_cost = 31.0 + + cls.DG2 = nx.DiGraph() + cls.DG2.add_weighted_edges_from( + { + ("A", "B", 3), + ("A", "C", 17), + ("A", "D", 14), + ("B", "A", 30), + ("B", "C", 2), + ("B", "D", 16), + ("C", "A", 33), + ("C", "B", 32), + ("C", "D", 34), + ("D", "A", 14), + ("D", "B", 15), + ("D", "C", 2), + } + ) + cls.DG2_cycle = ["D", "A", "B", "C", "D"] + cls.DG2_cost = 53.0 + + cls.unweightedUG = nx.complete_graph(5, nx.Graph()) + cls.unweightedDG = nx.complete_graph(5, nx.DiGraph()) + + cls.incompleteUG = nx.Graph() + cls.incompleteUG.add_weighted_edges_from({(0, 1, 1), (1, 2, 3)}) + cls.incompleteDG = nx.DiGraph() + cls.incompleteDG.add_weighted_edges_from({(0, 1, 1), (1, 2, 3)}) + + cls.UG = nx.Graph() + cls.UG.add_weighted_edges_from( + { + ("A", "B", 3), + ("A", "C", 17), + ("A", "D", 14), + ("B", "C", 12), + ("B", "D", 16), + ("C", "D", 4), + } + ) + cls.UG_cycle = ["D", "C", "B", "A", "D"] + cls.UG_cost = 33.0 + + cls.UG2 = nx.Graph() + cls.UG2.add_weighted_edges_from( + { + ("A", "B", 1), + ("A", "C", 15), + ("A", "D", 5), + ("B", "C", 16), + ("B", "D", 8), + ("C", "D", 3), + } + ) + cls.UG2_cycle = ["D", "C", "B", "A", "D"] + cls.UG2_cost = 25.0 + + +def validate_solution(soln, cost, exp_soln, exp_cost): + assert soln == exp_soln + assert cost == exp_cost + + +def validate_symmetric_solution(soln, cost, exp_soln, exp_cost): + assert soln == exp_soln or soln == exp_soln[::-1] + assert cost == exp_cost + + +class TestGreedyTSP(TestBase): + def test_greedy(self): + cycle = nx_app.greedy_tsp(self.DG, source="D") + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 31.0) + + cycle = nx_app.greedy_tsp(self.DG2, source="D") + cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 78.0) + + cycle = nx_app.greedy_tsp(self.UG, source="D") + cost = sum(self.UG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, ["D", "C", "B", "A", "D"], 33.0) + + cycle = nx_app.greedy_tsp(self.UG2, source="D") + cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, ["D", "C", "A", "B", "D"], 27.0) + + def test_not_complete_graph(self): + pytest.raises(nx.NetworkXError, nx_app.greedy_tsp, self.incompleteUG) + pytest.raises(nx.NetworkXError, nx_app.greedy_tsp, self.incompleteDG) + + def test_not_weighted_graph(self): + nx_app.greedy_tsp(self.unweightedUG) + nx_app.greedy_tsp(self.unweightedDG) + + def test_two_nodes(self): + G = nx.Graph() + G.add_weighted_edges_from({(1, 2, 1)}) + cycle = nx_app.greedy_tsp(G) + cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, [1, 2, 1], 2) + + def test_ignore_selfloops(self): + G = nx.complete_graph(5) + G.add_edge(3, 3) + cycle = nx_app.greedy_tsp(G) + assert len(cycle) - 1 == len(G) == len(set(cycle)) + + +class TestSimulatedAnnealingTSP(TestBase): + tsp = staticmethod(nx_app.simulated_annealing_tsp) + + def test_simulated_annealing_directed(self): + cycle = self.tsp(self.DG, "greedy", source="D", seed=42) + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.DG_cycle, self.DG_cost) + + initial_sol = ["D", "B", "A", "C", "D"] + cycle = self.tsp(self.DG, initial_sol, source="D", seed=42) + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.DG_cycle, self.DG_cost) + + initial_sol = ["D", "A", "C", "B", "D"] + cycle = self.tsp(self.DG, initial_sol, move="1-0", source="D", seed=42) + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.DG_cycle, self.DG_cost) + + cycle = self.tsp(self.DG2, "greedy", source="D", seed=42) + cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.DG2_cycle, self.DG2_cost) + + cycle = self.tsp(self.DG2, "greedy", move="1-0", source="D", seed=42) + cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.DG2_cycle, self.DG2_cost) + + def test_simulated_annealing_undirected(self): + cycle = self.tsp(self.UG, "greedy", source="D", seed=42) + cost = sum(self.UG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, self.UG_cycle, self.UG_cost) + + cycle = self.tsp(self.UG2, "greedy", source="D", seed=42) + cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_symmetric_solution(cycle, cost, self.UG2_cycle, self.UG2_cost) + + cycle = self.tsp(self.UG2, "greedy", move="1-0", source="D", seed=42) + cost = sum(self.UG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_symmetric_solution(cycle, cost, self.UG2_cycle, self.UG2_cost) + + def test_error_on_input_order_mistake(self): + # see issue #4846 https://github.com/networkx/networkx/issues/4846 + pytest.raises(TypeError, self.tsp, self.UG, weight="weight") + pytest.raises(nx.NetworkXError, self.tsp, self.UG, "weight") + + def test_not_complete_graph(self): + pytest.raises(nx.NetworkXError, self.tsp, self.incompleteUG, "greedy", source=0) + pytest.raises(nx.NetworkXError, self.tsp, self.incompleteDG, "greedy", source=0) + + def test_ignore_selfloops(self): + G = nx.complete_graph(5) + G.add_edge(3, 3) + cycle = self.tsp(G, "greedy") + assert len(cycle) - 1 == len(G) == len(set(cycle)) + + def test_not_weighted_graph(self): + self.tsp(self.unweightedUG, "greedy") + self.tsp(self.unweightedDG, "greedy") + + def test_two_nodes(self): + G = nx.Graph() + G.add_weighted_edges_from({(1, 2, 1)}) + + cycle = self.tsp(G, "greedy", source=1, seed=42) + cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, [1, 2, 1], 2) + + cycle = self.tsp(G, [1, 2, 1], source=1, seed=42) + cost = sum(G[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + validate_solution(cycle, cost, [1, 2, 1], 2) + + def test_failure_of_costs_too_high_when_iterations_low(self): + # Simulated Annealing Version: + # set number of moves low and alpha high + cycle = self.tsp( + self.DG2, "greedy", source="D", move="1-0", alpha=1, N_inner=1, seed=42 + ) + cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + assert cost > self.DG2_cost + + # Try with an incorrect initial guess + initial_sol = ["D", "A", "B", "C", "D"] + cycle = self.tsp( + self.DG, + initial_sol, + source="D", + move="1-0", + alpha=0.1, + N_inner=1, + max_iterations=1, + seed=42, + ) + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + assert cost > self.DG_cost + + +class TestThresholdAcceptingTSP(TestSimulatedAnnealingTSP): + tsp = staticmethod(nx_app.threshold_accepting_tsp) + + def test_failure_of_costs_too_high_when_iterations_low(self): + # Threshold Version: + # set number of moves low and number of iterations low + cycle = self.tsp( + self.DG2, + "greedy", + source="D", + move="1-0", + N_inner=1, + max_iterations=1, + seed=4, + ) + cost = sum(self.DG2[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + assert cost > self.DG2_cost + + # set threshold too low + initial_sol = ["D", "A", "B", "C", "D"] + cycle = self.tsp( + self.DG, initial_sol, source="D", move="1-0", threshold=-3, seed=42 + ) + cost = sum(self.DG[n][nbr]["weight"] for n, nbr in pairwise(cycle)) + assert cost > self.DG_cost + + +# Tests for function traveling_salesman_problem +def test_TSP_method(): + G = nx.cycle_graph(9) + G[4][5]["weight"] = 10 + + # Test using the old currying method + def sa_tsp(G, weight): + return nx_app.simulated_annealing_tsp(G, "greedy", weight, source=4, seed=1) + + path = nx_app.traveling_salesman_problem( + G, + method=sa_tsp, + cycle=False, + ) + assert path == [4, 3, 2, 1, 0, 8, 7, 6, 5] + + +def test_TSP_unweighted(): + G = nx.cycle_graph(9) + path = nx_app.traveling_salesman_problem(G, nodes=[3, 6], cycle=False) + assert path in ([3, 4, 5, 6], [6, 5, 4, 3]) + + cycle = nx_app.traveling_salesman_problem(G, nodes=[3, 6]) + assert cycle in ([3, 4, 5, 6, 5, 4, 3], [6, 5, 4, 3, 4, 5, 6]) + + +def test_TSP_weighted(): + G = nx.cycle_graph(9) + G[0][1]["weight"] = 2 + G[1][2]["weight"] = 2 + G[2][3]["weight"] = 2 + G[3][4]["weight"] = 4 + G[4][5]["weight"] = 5 + G[5][6]["weight"] = 4 + G[6][7]["weight"] = 2 + G[7][8]["weight"] = 2 + G[8][0]["weight"] = 2 + tsp = nx_app.traveling_salesman_problem + + # path between 3 and 6 + expected_paths = ([3, 2, 1, 0, 8, 7, 6], [6, 7, 8, 0, 1, 2, 3]) + # cycle between 3 and 6 + expected_cycles = ( + [3, 2, 1, 0, 8, 7, 6, 7, 8, 0, 1, 2, 3], + [6, 7, 8, 0, 1, 2, 3, 2, 1, 0, 8, 7, 6], + ) + # path through all nodes + expected_tourpaths = ([5, 6, 7, 8, 0, 1, 2, 3, 4], [4, 3, 2, 1, 0, 8, 7, 6, 5]) + + # Check default method + cycle = tsp(G, nodes=[3, 6], weight="weight") + assert cycle in expected_cycles + + path = tsp(G, nodes=[3, 6], weight="weight", cycle=False) + assert path in expected_paths + + tourpath = tsp(G, weight="weight", cycle=False) + assert tourpath in expected_tourpaths + + # Check all methods + methods = [ + (nx_app.christofides, {}), + (nx_app.greedy_tsp, {}), + ( + nx_app.simulated_annealing_tsp, + {"init_cycle": "greedy"}, + ), + ( + nx_app.threshold_accepting_tsp, + {"init_cycle": "greedy"}, + ), + ] + for method, kwargs in methods: + cycle = tsp(G, nodes=[3, 6], weight="weight", method=method, **kwargs) + assert cycle in expected_cycles + + path = tsp( + G, nodes=[3, 6], weight="weight", method=method, cycle=False, **kwargs + ) + assert path in expected_paths + + tourpath = tsp(G, weight="weight", method=method, cycle=False, **kwargs) + assert tourpath in expected_tourpaths + + +def test_TSP_incomplete_graph_short_path(): + G = nx.cycle_graph(9) + G.add_edges_from([(4, 9), (9, 10), (10, 11), (11, 0)]) + G[4][5]["weight"] = 5 + + cycle = nx_app.traveling_salesman_problem(G) + assert len(cycle) == 17 and len(set(cycle)) == 12 + + # make sure that cutting one edge out of complete graph formulation + # cuts out many edges out of the path of the TSP + path = nx_app.traveling_salesman_problem(G, cycle=False) + assert len(path) == 13 and len(set(path)) == 12 + + +def test_TSP_alternate_weight(): + G = nx.complete_graph(9) + G[0][1]["weight"] = 2 + G[1][2]["weight"] = 2 + G[2][3]["weight"] = 2 + G[3][4]["weight"] = 4 + G[4][5]["weight"] = 5 + G[5][6]["weight"] = 4 + G[6][7]["weight"] = 2 + G[7][8]["weight"] = 2 + G[8][0]["weight"] = 2 + + H = nx.complete_graph(9) + H[0][1]["distance"] = 2 + H[1][2]["distance"] = 2 + H[2][3]["distance"] = 2 + H[3][4]["distance"] = 4 + H[4][5]["distance"] = 5 + H[5][6]["distance"] = 4 + H[6][7]["distance"] = 2 + H[7][8]["distance"] = 2 + H[8][0]["distance"] = 2 + + assert nx_app.traveling_salesman_problem( + G, weight="weight" + ) == nx_app.traveling_salesman_problem(H, weight="distance") + + +def test_held_karp_ascent(): + """ + Test the Held-Karp relaxation with the ascent method + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + # Adjacency matrix from page 1153 of the 1970 Held and Karp paper + # which have been edited to be directional, but also symmetric + G_array = np.array( + [ + [0, 97, 60, 73, 17, 52], + [97, 0, 41, 52, 90, 30], + [60, 41, 0, 21, 35, 41], + [73, 52, 21, 0, 95, 46], + [17, 90, 35, 95, 0, 81], + [52, 30, 41, 46, 81, 0], + ] + ) + + solution_edges = [(1, 3), (2, 4), (3, 2), (4, 0), (5, 1), (0, 5)] + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + # Check that the optimal weights are the same + assert round(opt_hk, 2) == 207.00 + # Check that the z_stars are the same + solution = nx.DiGraph() + solution.add_edges_from(solution_edges) + # Use undirected edges for `edges_equal` because the graph is symmetric. + assert nx.utils.edges_equal(z_star.edges, solution.edges) + + +def test_ascent_fractional_solution(): + """ + Test the ascent method using a modified version of Figure 2 on page 1140 + in 'The Traveling Salesman Problem and Minimum Spanning Trees' by Held and + Karp + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + # This version of Figure 2 has all of the edge weights multiplied by 100 + # and is a complete directed graph with infinite edge weights for the + # edges not listed in the original graph + G_array = np.array( + [ + [0, 100, 100, 100000, 100000, 1], + [100, 0, 100, 100000, 1, 100000], + [100, 100, 0, 1, 100000, 100000], + [100000, 100000, 1, 0, 100, 100], + [100000, 1, 100000, 100, 0, 100], + [1, 100000, 100000, 100, 100, 0], + ] + ) + + solution_z_star = { + (0, 1): 5 / 12, + (0, 2): 5 / 12, + (0, 5): 5 / 6, + (1, 0): 5 / 12, + (1, 2): 1 / 3, + (1, 4): 5 / 6, + (2, 0): 5 / 12, + (2, 1): 1 / 3, + (2, 3): 5 / 6, + (3, 2): 5 / 6, + (3, 4): 1 / 3, + (3, 5): 1 / 2, + (4, 1): 5 / 6, + (4, 3): 1 / 3, + (4, 5): 1 / 2, + (5, 0): 5 / 6, + (5, 3): 1 / 2, + (5, 4): 1 / 2, + } + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + # Check that the optimal weights are the same + assert round(opt_hk, 2) == 303.00 + # Check that the z_stars are the same + assert {key: round(z_star[key], 4) for key in z_star} == { + key: round(solution_z_star[key], 4) for key in solution_z_star + } + + +def test_ascent_method_asymmetric(): + """ + Tests the ascent method using a truly asymmetric graph for which the + solution has been brute forced + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + [0, 26, 63, 59, 69, 31, 41], + [62, 0, 91, 53, 75, 87, 47], + [47, 82, 0, 90, 15, 9, 18], + [68, 19, 5, 0, 58, 34, 93], + [11, 58, 53, 55, 0, 61, 79], + [88, 75, 13, 76, 98, 0, 40], + [41, 61, 55, 88, 46, 45, 0], + ] + ) + + solution_edges = [(0, 1), (1, 3), (3, 2), (2, 5), (5, 6), (4, 0), (6, 4)] + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + # Check that the optimal weights are the same + assert round(opt_hk, 2) == 190.00 + # Check that the z_stars match. + solution = nx.DiGraph() + solution.add_edges_from(solution_edges) + assert nx.utils.edges_equal(z_star.edges, solution.edges, directed=True) + + +def test_ascent_method_asymmetric_2(): + """ + Tests the ascent method using a truly asymmetric graph for which the + solution has been brute forced + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + [0, 45, 39, 92, 29, 31], + [72, 0, 4, 12, 21, 60], + [81, 6, 0, 98, 70, 53], + [49, 71, 59, 0, 98, 94], + [74, 95, 24, 43, 0, 47], + [56, 43, 3, 65, 22, 0], + ] + ) + + solution_edges = [(0, 5), (5, 4), (1, 3), (3, 0), (2, 1), (4, 2)] + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + # Check that the optimal weights are the same + assert round(opt_hk, 2) == 144.00 + # Check that the z_stars match. + solution = nx.DiGraph() + solution.add_edges_from(solution_edges) + assert nx.utils.edges_equal(z_star.edges, solution.edges, directed=True) + + +def test_held_karp_ascent_asymmetric_3(): + """ + Tests the ascent method using a truly asymmetric graph with a fractional + solution for which the solution has been brute forced. + + In this graph their are two different optimal, integral solutions (which + are also the overall atsp solutions) to the Held Karp relaxation. However, + this particular graph has two different tours of optimal value and the + possible solutions in the held_karp_ascent function are not stored in an + ordered data structure. + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + [0, 1, 5, 2, 7, 4], + [7, 0, 7, 7, 1, 4], + [4, 7, 0, 9, 2, 1], + [7, 2, 7, 0, 4, 4], + [5, 5, 4, 4, 0, 3], + [3, 9, 1, 3, 4, 0], + ] + ) + + solution1_edges = [(0, 3), (1, 4), (2, 5), (3, 1), (4, 2), (5, 0)] + + solution2_edges = [(0, 3), (3, 1), (1, 4), (4, 5), (2, 0), (5, 2)] + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + assert round(opt_hk, 2) == 13.00 + # Check that the z_stars are the same + solution1 = nx.DiGraph() + solution1.add_edges_from(solution1_edges) + solution2 = nx.DiGraph() + solution2.add_edges_from(solution2_edges) + assert nx.utils.edges_equal( + z_star.edges, solution1.edges, directed=True + ) or nx.utils.edges_equal(z_star.edges, solution2.edges, directed=True) + + +def test_held_karp_ascent_fractional_asymmetric(): + """ + Tests the ascent method using a truly asymmetric graph with a fractional + solution for which the solution has been brute forced + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + [0, 100, 150, 100000, 100000, 1], + [150, 0, 100, 100000, 1, 100000], + [100, 150, 0, 1, 100000, 100000], + [100000, 100000, 1, 0, 150, 100], + [100000, 2, 100000, 100, 0, 150], + [2, 100000, 100000, 150, 100, 0], + ] + ) + + solution_z_star = { + (0, 1): 5 / 12, + (0, 2): 5 / 12, + (0, 5): 5 / 6, + (1, 0): 5 / 12, + (1, 2): 5 / 12, + (1, 4): 5 / 6, + (2, 0): 5 / 12, + (2, 1): 5 / 12, + (2, 3): 5 / 6, + (3, 2): 5 / 6, + (3, 4): 5 / 12, + (3, 5): 5 / 12, + (4, 1): 5 / 6, + (4, 3): 5 / 12, + (4, 5): 5 / 12, + (5, 0): 5 / 6, + (5, 3): 5 / 12, + (5, 4): 5 / 12, + } + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + opt_hk, z_star = tsp.held_karp_ascent(G) + + # Check that the optimal weights are the same + assert round(opt_hk, 2) == 304.00 + # Check that the z_stars are the same + assert {key: round(z_star[key], 4) for key in z_star} == { + key: round(solution_z_star[key], 4) for key in solution_z_star + } + + +def test_spanning_tree_distribution(): + """ + Test that we can create an exponential distribution of spanning trees such + that the probability of each tree is proportional to the product of edge + weights. + + Results of this test have been confirmed with hypothesis testing from the + created distribution. + + This test uses the symmetric, fractional Held Karp solution. + """ + import networkx.algorithms.approximation.traveling_salesman as tsp + + pytest.importorskip("numpy") + pytest.importorskip("scipy") + + z_star = { + (0, 1): 5 / 12, + (0, 2): 5 / 12, + (0, 5): 5 / 6, + (1, 0): 5 / 12, + (1, 2): 1 / 3, + (1, 4): 5 / 6, + (2, 0): 5 / 12, + (2, 1): 1 / 3, + (2, 3): 5 / 6, + (3, 2): 5 / 6, + (3, 4): 1 / 3, + (3, 5): 1 / 2, + (4, 1): 5 / 6, + (4, 3): 1 / 3, + (4, 5): 1 / 2, + (5, 0): 5 / 6, + (5, 3): 1 / 2, + (5, 4): 1 / 2, + } + + solution_gamma = { + (0, 1): -0.6383, + (0, 2): -0.6827, + (0, 5): 0, + (1, 2): -1.0781, + (1, 4): 0, + (2, 3): 0, + (5, 3): -0.2820, + (5, 4): -0.3327, + (4, 3): -0.9927, + } + + # The undirected support of z_star + G = nx.MultiGraph() + for u, v in z_star: + if (u, v) in G.edges or (v, u) in G.edges: + continue + G.add_edge(u, v) + + gamma = tsp.spanning_tree_distribution(G, z_star) + + assert {key: round(gamma[key], 4) for key in gamma} == solution_gamma + + +def test_asadpour_tsp(): + """ + Test the complete asadpour tsp algorithm with the fractional, symmetric + Held Karp solution. This test also uses an incomplete graph as input. + """ + # This version of Figure 2 has all of the edge weights multiplied by 100 + # and the 0 weight edges have a weight of 1. + pytest.importorskip("numpy") + pytest.importorskip("scipy") + + edge_list = [ + (0, 1, 100), + (0, 2, 100), + (0, 5, 1), + (1, 2, 100), + (1, 4, 1), + (2, 3, 1), + (3, 4, 100), + (3, 5, 100), + (4, 5, 100), + (1, 0, 100), + (2, 0, 100), + (5, 0, 1), + (2, 1, 100), + (4, 1, 1), + (3, 2, 1), + (4, 3, 100), + (5, 3, 100), + (5, 4, 100), + ] + + G = nx.DiGraph() + G.add_weighted_edges_from(edge_list) + + tour = nx_app.traveling_salesman_problem( + G, weight="weight", method=nx_app.asadpour_atsp, seed=19 + ) + + # Check that the returned list is a valid tour. Because this is an + # incomplete graph, the conditions are not as strict. We need the tour to + # + # Start and end at the same node + # Pass through every vertex at least once + # Have a total cost at most ln(6) / ln(ln(6)) = 3.0723 times the optimal + # + # For the second condition it is possible to have the tour pass through the + # same vertex more then. Imagine that the tour on the complete version takes + # an edge not in the original graph. In the output this is substituted with + # the shortest path between those vertices, allowing vertices to appear more + # than once. + # + # Even though we are using a fixed seed, multiple tours have been known to + # be returned. The first two are from the original development of this test, + # and the third one from issue #5913 on GitHub. If other tours are returned, + # add it on the list of expected tours. + expected_tours = [ + [1, 4, 5, 0, 2, 3, 2, 1], + [3, 2, 0, 1, 4, 5, 3], + [3, 2, 1, 0, 5, 4, 3], + ] + + assert tour in expected_tours + + +def test_asadpour_real_world(): + """ + This test uses airline prices between the six largest cities in the US. + + * New York City -> JFK + * Los Angeles -> LAX + * Chicago -> ORD + * Houston -> IAH + * Phoenix -> PHX + * Philadelphia -> PHL + + Flight prices from August 2021 using Delta or American airlines to get + nonstop flight. The brute force solution found the optimal tour to cost $872 + + This test also uses the `source` keyword argument to ensure that the tour + always starts at city 0. + """ + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + # JFK LAX ORD IAH PHX PHL + [0, 243, 199, 208, 169, 183], # JFK + [277, 0, 217, 123, 127, 252], # LAX + [297, 197, 0, 197, 123, 177], # ORD + [303, 169, 197, 0, 117, 117], # IAH + [257, 127, 160, 117, 0, 319], # PHX + [183, 332, 217, 117, 319, 0], # PHL + ] + ) + + node_list = ["JFK", "LAX", "ORD", "IAH", "PHX", "PHL"] + + expected_tours = [ + ["JFK", "LAX", "PHX", "ORD", "IAH", "PHL", "JFK"], + ["JFK", "ORD", "PHX", "LAX", "IAH", "PHL", "JFK"], + ] + + G = nx.from_numpy_array(G_array, nodelist=node_list, create_using=nx.DiGraph) + + tour = nx_app.traveling_salesman_problem( + G, weight="weight", method=nx_app.asadpour_atsp, seed=37, source="JFK" + ) + + assert tour in expected_tours + + +def test_asadpour_real_world_path(): + """ + This test uses airline prices between the six largest cities in the US. This + time using a path, not a cycle. + + * New York City -> JFK + * Los Angeles -> LAX + * Chicago -> ORD + * Houston -> IAH + * Phoenix -> PHX + * Philadelphia -> PHL + + Flight prices from August 2021 using Delta or American airlines to get + nonstop flight. The brute force solution found the optimal tour to cost $872 + """ + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + G_array = np.array( + [ + # JFK LAX ORD IAH PHX PHL + [0, 243, 199, 208, 169, 183], # JFK + [277, 0, 217, 123, 127, 252], # LAX + [297, 197, 0, 197, 123, 177], # ORD + [303, 169, 197, 0, 117, 117], # IAH + [257, 127, 160, 117, 0, 319], # PHX + [183, 332, 217, 117, 319, 0], # PHL + ] + ) + + node_list = ["JFK", "LAX", "ORD", "IAH", "PHX", "PHL"] + + expected_paths = [ + ["ORD", "PHX", "LAX", "IAH", "PHL", "JFK"], + ["JFK", "PHL", "IAH", "ORD", "PHX", "LAX"], + ] + + G = nx.from_numpy_array(G_array, nodelist=node_list, create_using=nx.DiGraph) + + path = nx_app.traveling_salesman_problem( + G, weight="weight", cycle=False, method=nx_app.asadpour_atsp, seed=56 + ) + + assert path in expected_paths + + +def test_asadpour_disconnected_graph(): + """ + Test that the proper exception is raised when asadpour_atsp is given an + disconnected graph. + """ + + G = nx.complete_graph(4, create_using=nx.DiGraph) + # have to set edge weights so that if the exception is not raised, the + # function will complete and we will fail the test + nx.set_edge_attributes(G, 1, "weight") + G.add_node(5) + + pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G) + + +def test_asadpour_incomplete_graph(): + """ + Test that the proper exception is raised when asadpour_atsp is given an + incomplete graph + """ + + G = nx.complete_graph(4, create_using=nx.DiGraph) + # have to set edge weights so that if the exception is not raised, the + # function will complete and we will fail the test + nx.set_edge_attributes(G, 1, "weight") + G.remove_edge(0, 1) + + pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G) + + +def test_asadpour_empty_graph(): + """ + Test the asadpour_atsp function with an empty graph + """ + G = nx.DiGraph() + + pytest.raises(nx.NetworkXError, nx_app.asadpour_atsp, G) + + +def test_asadpour_small_graphs(): + # 1 node + G = nx.path_graph(1, create_using=nx.DiGraph) + with pytest.raises(nx.NetworkXError, match="at least two nodes"): + nx_app.asadpour_atsp(G) + + # 2 nodes + G = nx.DiGraph() + G.add_weighted_edges_from([(0, 1, 7), (1, 0, 8)]) + assert nx_app.asadpour_atsp(G) in [[0, 1], [1, 0]] + assert nx_app.asadpour_atsp(G, source=1) == [1, 0] + assert nx_app.asadpour_atsp(G, source=0) == [0, 1] + + +@pytest.mark.slow +def test_asadpour_integral_held_karp(): + """ + This test uses an integral held karp solution and the held karp function + will return a graph rather than a dict, bypassing most of the asadpour + algorithm. + + At first glance, this test probably doesn't look like it ensures that we + skip the rest of the asadpour algorithm, but it does. We are not fixing a + see for the random number generator, so if we sample any spanning trees + the approximation would be different basically every time this test is + executed but it is not since held karp is deterministic and we do not + reach the portion of the code with the dependence on random numbers. + """ + np = pytest.importorskip("numpy") + + G_array = np.array( + [ + [0, 26, 63, 59, 69, 31, 41], + [62, 0, 91, 53, 75, 87, 47], + [47, 82, 0, 90, 15, 9, 18], + [68, 19, 5, 0, 58, 34, 93], + [11, 58, 53, 55, 0, 61, 79], + [88, 75, 13, 76, 98, 0, 40], + [41, 61, 55, 88, 46, 45, 0], + ] + ) + + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + + for _ in range(2): + tour = nx_app.traveling_salesman_problem(G, method=nx_app.asadpour_atsp) + + assert [1, 3, 2, 5, 2, 6, 4, 0, 1] == tour + + +def test_directed_tsp_impossible(): + """ + Test the asadpour algorithm with a graph without a hamiltonian circuit + """ + pytest.importorskip("numpy") + + # In this graph, once we leave node 0 we cannot return + edges = [ + (0, 1, 10), + (0, 2, 11), + (0, 3, 12), + (1, 2, 4), + (1, 3, 6), + (2, 1, 3), + (2, 3, 2), + (3, 1, 5), + (3, 2, 1), + ] + + G = nx.DiGraph() + G.add_weighted_edges_from(edges) + + pytest.raises(nx.NetworkXError, nx_app.traveling_salesman_problem, G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_treewidth.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_treewidth.py new file mode 100644 index 0000000000000000000000000000000000000000..c40910ec8b6d98d3e2406a4761a751ee5c8f0ccd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_treewidth.py @@ -0,0 +1,274 @@ +import itertools + +import networkx as nx +from networkx.algorithms.approximation import ( + treewidth_min_degree, + treewidth_min_fill_in, +) +from networkx.algorithms.approximation.treewidth import ( + MinDegreeHeuristic, + min_fill_in_heuristic, +) + + +def is_tree_decomp(graph, decomp): + """Check if the given tree decomposition is valid.""" + for x in graph.nodes(): + appear_once = False + for bag in decomp.nodes(): + if x in bag: + appear_once = True + break + assert appear_once + + # Check if each connected pair of nodes are at least once together in a bag + for x, y in graph.edges(): + appear_together = False + for bag in decomp.nodes(): + if x in bag and y in bag: + appear_together = True + break + assert appear_together + + # Check if the nodes associated with vertex v form a connected subset of T + for v in graph.nodes(): + subset = [] + for bag in decomp.nodes(): + if v in bag: + subset.append(bag) + sub_graph = decomp.subgraph(subset) + assert nx.is_connected(sub_graph) + + +class TestTreewidthMinDegree: + """Unit tests for the min_degree function""" + + @classmethod + def setup_class(cls): + """Setup for different kinds of trees""" + cls.complete = nx.Graph() + cls.complete.add_edge(1, 2) + cls.complete.add_edge(2, 3) + cls.complete.add_edge(1, 3) + + cls.small_tree = nx.Graph() + cls.small_tree.add_edge(1, 3) + cls.small_tree.add_edge(4, 3) + cls.small_tree.add_edge(2, 3) + cls.small_tree.add_edge(3, 5) + cls.small_tree.add_edge(5, 6) + cls.small_tree.add_edge(5, 7) + cls.small_tree.add_edge(6, 7) + + cls.deterministic_graph = nx.Graph() + cls.deterministic_graph.add_edge(0, 1) # deg(0) = 1 + + cls.deterministic_graph.add_edge(1, 2) # deg(1) = 2 + + cls.deterministic_graph.add_edge(2, 3) + cls.deterministic_graph.add_edge(2, 4) # deg(2) = 3 + + cls.deterministic_graph.add_edge(3, 4) + cls.deterministic_graph.add_edge(3, 5) + cls.deterministic_graph.add_edge(3, 6) # deg(3) = 4 + + cls.deterministic_graph.add_edge(4, 5) + cls.deterministic_graph.add_edge(4, 6) + cls.deterministic_graph.add_edge(4, 7) # deg(4) = 5 + + cls.deterministic_graph.add_edge(5, 6) + cls.deterministic_graph.add_edge(5, 7) + cls.deterministic_graph.add_edge(5, 8) + cls.deterministic_graph.add_edge(5, 9) # deg(5) = 6 + + cls.deterministic_graph.add_edge(6, 7) + cls.deterministic_graph.add_edge(6, 8) + cls.deterministic_graph.add_edge(6, 9) # deg(6) = 6 + + cls.deterministic_graph.add_edge(7, 8) + cls.deterministic_graph.add_edge(7, 9) # deg(7) = 5 + + cls.deterministic_graph.add_edge(8, 9) # deg(8) = 4 + + def test_petersen_graph(self): + """Test Petersen graph tree decomposition result""" + G = nx.petersen_graph() + _, decomp = treewidth_min_degree(G) + is_tree_decomp(G, decomp) + + def test_small_tree_treewidth(self): + """Test small tree + + Test if the computed treewidth of the known self.small_tree is 2. + As we know which value we can expect from our heuristic, values other + than two are regressions + """ + G = self.small_tree + # the order of removal should be [1,2,4]3[5,6,7] + # (with [] denoting any order of the containing nodes) + # resulting in treewidth 2 for the heuristic + treewidth, _ = treewidth_min_fill_in(G) + assert treewidth == 2 + + def test_heuristic_abort(self): + """Test heuristic abort condition for fully connected graph""" + graph = {} + for u in self.complete: + graph[u] = set() + for v in self.complete[u]: + if u != v: # ignore self-loop + graph[u].add(v) + + deg_heuristic = MinDegreeHeuristic(graph) + node = deg_heuristic.best_node(graph) + if node is None: + pass + else: + assert False + + def test_empty_graph(self): + """Test empty graph""" + G = nx.Graph() + _, _ = treewidth_min_degree(G) + + def test_two_component_graph(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + treewidth, _ = treewidth_min_degree(G) + assert treewidth == 0 + + def test_not_sortable_nodes(self): + G = nx.Graph([(0, "a")]) + treewidth_min_degree(G) + + def test_heuristic_first_steps(self): + """Test first steps of min_degree heuristic""" + graph = { + n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph + } + deg_heuristic = MinDegreeHeuristic(graph) + elim_node = deg_heuristic.best_node(graph) + steps = [] + + while elim_node is not None: + steps.append(elim_node) + nbrs = graph[elim_node] + + for u, v in itertools.permutations(nbrs, 2): + if v not in graph[u]: + graph[u].add(v) + + for u in graph: + if elim_node in graph[u]: + graph[u].remove(elim_node) + + del graph[elim_node] + elim_node = deg_heuristic.best_node(graph) + + # check only the first 5 elements for equality + assert steps[:5] == [0, 1, 2, 3, 4] + + +class TestTreewidthMinFillIn: + """Unit tests for the treewidth_min_fill_in function.""" + + @classmethod + def setup_class(cls): + """Setup for different kinds of trees""" + cls.complete = nx.Graph() + cls.complete.add_edge(1, 2) + cls.complete.add_edge(2, 3) + cls.complete.add_edge(1, 3) + + cls.small_tree = nx.Graph() + cls.small_tree.add_edge(1, 2) + cls.small_tree.add_edge(2, 3) + cls.small_tree.add_edge(3, 4) + cls.small_tree.add_edge(1, 4) + cls.small_tree.add_edge(2, 4) + cls.small_tree.add_edge(4, 5) + cls.small_tree.add_edge(5, 6) + cls.small_tree.add_edge(5, 7) + cls.small_tree.add_edge(6, 7) + + cls.deterministic_graph = nx.Graph() + cls.deterministic_graph.add_edge(1, 2) + cls.deterministic_graph.add_edge(1, 3) + cls.deterministic_graph.add_edge(3, 4) + cls.deterministic_graph.add_edge(2, 4) + cls.deterministic_graph.add_edge(3, 5) + cls.deterministic_graph.add_edge(4, 5) + cls.deterministic_graph.add_edge(3, 6) + cls.deterministic_graph.add_edge(5, 6) + + def test_petersen_graph(self): + """Test Petersen graph tree decomposition result""" + G = nx.petersen_graph() + _, decomp = treewidth_min_fill_in(G) + is_tree_decomp(G, decomp) + + def test_small_tree_treewidth(self): + """Test if the computed treewidth of the known self.small_tree is 2""" + G = self.small_tree + # the order of removal should be [1,2,4]3[5,6,7] + # (with [] denoting any order of the containing nodes) + # resulting in treewidth 2 for the heuristic + treewidth, _ = treewidth_min_fill_in(G) + assert treewidth == 2 + + def test_heuristic_abort(self): + """Test if min_fill_in returns None for fully connected graph""" + graph = {} + for u in self.complete: + graph[u] = set() + for v in self.complete[u]: + if u != v: # ignore self-loop + graph[u].add(v) + next_node = min_fill_in_heuristic(graph) + if next_node is None: + pass + else: + assert False + + def test_empty_graph(self): + """Test empty graph""" + G = nx.Graph() + _, _ = treewidth_min_fill_in(G) + + def test_two_component_graph(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + treewidth, _ = treewidth_min_fill_in(G) + assert treewidth == 0 + + def test_not_sortable_nodes(self): + G = nx.Graph([(0, "a")]) + treewidth_min_fill_in(G) + + def test_heuristic_first_steps(self): + """Test first steps of min_fill_in heuristic""" + graph = { + n: set(self.deterministic_graph[n]) - {n} for n in self.deterministic_graph + } + elim_node = min_fill_in_heuristic(graph) + steps = [] + + while elim_node is not None: + steps.append(elim_node) + nbrs = graph[elim_node] + + for u, v in itertools.permutations(nbrs, 2): + if v not in graph[u]: + graph[u].add(v) + + for u in graph: + if elim_node in graph[u]: + graph[u].remove(elim_node) + + del graph[elim_node] + elim_node = min_fill_in_heuristic(graph) + + # check only the first 2 elements for equality + assert steps[:2] == [6, 5] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py new file mode 100644 index 0000000000000000000000000000000000000000..5cc5a38df9a4139684005491e0183cd563487154 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/tests/test_vertex_cover.py @@ -0,0 +1,68 @@ +import networkx as nx +from networkx.algorithms.approximation import min_weighted_vertex_cover + + +def is_cover(G, node_cover): + return all({u, v} & node_cover for u, v in G.edges()) + + +class TestMWVC: + """Unit tests for the approximate minimum weighted vertex cover + function, + :func:`~networkx.algorithms.approximation.vertex_cover.min_weighted_vertex_cover`. + + """ + + def test_unweighted_directed(self): + # Create a star graph in which half the nodes are directed in + # and half are directed out. + G = nx.DiGraph() + G.add_edges_from((0, v) for v in range(1, 26)) + G.add_edges_from((v, 0) for v in range(26, 51)) + cover = min_weighted_vertex_cover(G) + assert 1 == len(cover) + assert is_cover(G, cover) + + def test_unweighted_undirected(self): + # create a simple star graph + size = 50 + sg = nx.star_graph(size) + cover = min_weighted_vertex_cover(sg) + assert 1 == len(cover) + assert is_cover(sg, cover) + + def test_weighted(self): + wg = nx.Graph() + wg.add_node(0, weight=10) + wg.add_node(1, weight=1) + wg.add_node(2, weight=1) + wg.add_node(3, weight=1) + wg.add_node(4, weight=1) + + wg.add_edge(0, 1) + wg.add_edge(0, 2) + wg.add_edge(0, 3) + wg.add_edge(0, 4) + + wg.add_edge(1, 2) + wg.add_edge(2, 3) + wg.add_edge(3, 4) + wg.add_edge(4, 1) + + cover = min_weighted_vertex_cover(wg, weight="weight") + csum = sum(wg.nodes[node]["weight"] for node in cover) + assert 4 == csum + assert is_cover(wg, cover) + + def test_unweighted_self_loop(self): + slg = nx.Graph() + slg.add_node(0) + slg.add_node(1) + slg.add_node(2) + + slg.add_edge(0, 1) + slg.add_edge(2, 2) + + cover = min_weighted_vertex_cover(slg) + assert 2 == len(cover) + assert is_cover(slg, cover) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/traveling_salesman.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/traveling_salesman.py new file mode 100644 index 0000000000000000000000000000000000000000..0c6dd52281bc90b7eafbc0fee694771b59a5bfaa --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/traveling_salesman.py @@ -0,0 +1,1508 @@ +""" +================================= +Travelling Salesman Problem (TSP) +================================= + +Implementation of approximate algorithms +for solving and approximating the TSP problem. + +Categories of algorithms which are implemented: + +- Christofides (provides a 3/2-approximation of TSP) +- Greedy +- Simulated Annealing (SA) +- Threshold Accepting (TA) +- Asadpour Asymmetric Traveling Salesman Algorithm + +The Travelling Salesman Problem tries to find, given the weight +(distance) between all points where a salesman has to visit, the +route so that: + +- The total distance (cost) which the salesman travels is minimized. +- The salesman returns to the starting point. +- Note that for a complete graph, the salesman visits each point once. + +The function `travelling_salesman_problem` allows for incomplete +graphs by finding all-pairs shortest paths, effectively converting +the problem to a complete graph problem. It calls one of the +approximate methods on that problem and then converts the result +back to the original graph using the previously found shortest paths. + +TSP is an NP-hard problem in combinatorial optimization, +important in operations research and theoretical computer science. + +http://en.wikipedia.org/wiki/Travelling_salesman_problem +""" + +import math + +import networkx as nx +from networkx.algorithms.tree.mst import random_spanning_tree +from networkx.utils import not_implemented_for, pairwise, py_random_state + +__all__ = [ + "traveling_salesman_problem", + "christofides", + "asadpour_atsp", + "greedy_tsp", + "simulated_annealing_tsp", + "threshold_accepting_tsp", +] + + +def swap_two_nodes(soln, seed): + """Swap two nodes in `soln` to give a neighbor solution. + + Parameters + ---------- + soln : list of nodes + Current cycle of nodes + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + list + The solution after move is applied. (A neighbor solution.) + + Notes + ----- + This function assumes that the incoming list `soln` is a cycle + (that the first and last element are the same) and also that + we don't want any move to change the first node in the list + (and thus not the last node either). + + The input list is changed as well as returned. Make a copy if needed. + + See Also + -------- + move_one_node + """ + a, b = seed.sample(range(1, len(soln) - 1), k=2) + soln[a], soln[b] = soln[b], soln[a] + return soln + + +def move_one_node(soln, seed): + """Move one node to another position to give a neighbor solution. + + The node to move and the position to move to are chosen randomly. + The first and last nodes are left untouched as soln must be a cycle + starting at that node. + + Parameters + ---------- + soln : list of nodes + Current cycle of nodes + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + list + The solution after move is applied. (A neighbor solution.) + + Notes + ----- + This function assumes that the incoming list `soln` is a cycle + (that the first and last element are the same) and also that + we don't want any move to change the first node in the list + (and thus not the last node either). + + The input list is changed as well as returned. Make a copy if needed. + + See Also + -------- + swap_two_nodes + """ + a, b = seed.sample(range(1, len(soln) - 1), k=2) + soln.insert(b, soln.pop(a)) + return soln + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def christofides(G, weight="weight", tree=None): + """Approximate a solution of the traveling salesman problem + + Compute a 3/2-approximation of the traveling salesman problem + in a complete undirected graph using Christofides [1]_ algorithm. + + Parameters + ---------- + G : Graph + `G` should be a complete weighted undirected graph. + The distance between all pairs of nodes should be included. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + tree : NetworkX graph or None (default: None) + A minimum spanning tree of G. Or, if None, the minimum spanning + tree is computed using :func:`networkx.minimum_spanning_tree` + + Returns + ------- + list + List of nodes in `G` along a cycle with a 3/2-approximation of + the minimal Hamiltonian cycle. + + References + ---------- + .. [1] Christofides, Nicos. "Worst-case analysis of a new heuristic for + the travelling salesman problem." No. RR-388. Carnegie-Mellon Univ + Pittsburgh Pa Management Sciences Research Group, 1976. + """ + # Remove selfloops if necessary + loop_nodes = nx.nodes_with_selfloops(G) + try: + node = next(loop_nodes) + except StopIteration: + pass + else: + G = G.copy() + G.remove_edge(node, node) + G.remove_edges_from((n, n) for n in loop_nodes) + # Check that G is a complete graph + N = len(G) - 1 + # This check ignores selfloops which is what we want here. + if any(len(nbrdict) != N for n, nbrdict in G.adj.items()): + raise nx.NetworkXError("G must be a complete graph.") + + if tree is None: + tree = nx.minimum_spanning_tree(G, weight=weight) + L = G.copy() + L.remove_nodes_from([v for v, degree in tree.degree if not (degree % 2)]) + MG = nx.MultiGraph() + MG.add_edges_from(tree.edges) + edges = nx.min_weight_matching(L, weight=weight) + MG.add_edges_from(edges) + return _shortcutting(nx.eulerian_circuit(MG)) + + +def _shortcutting(circuit): + """Remove duplicate nodes in the path""" + nodes = [] + for u, v in circuit: + if v in nodes: + continue + if not nodes: + nodes.append(u) + nodes.append(v) + nodes.append(nodes[0]) + return nodes + + +@nx._dispatchable(edge_attrs="weight") +def traveling_salesman_problem( + G, weight="weight", nodes=None, cycle=True, method=None, **kwargs +): + """Find the shortest path in `G` connecting specified nodes + + This function allows approximate solution to the traveling salesman + problem on networks that are not complete graphs and/or where the + salesman does not need to visit all nodes. + + This function proceeds in two steps. First, it creates a complete + graph using the all-pairs shortest_paths between nodes in `nodes`. + Edge weights in the new graph are the lengths of the paths + between each pair of nodes in the original graph. + Second, an algorithm (default: `christofides` for undirected and + `asadpour_atsp` for directed) is used to approximate the minimal Hamiltonian + cycle on this new graph. The available algorithms are: + + - christofides + - greedy_tsp + - simulated_annealing_tsp + - threshold_accepting_tsp + - asadpour_atsp + + Once the Hamiltonian Cycle is found, this function post-processes to + accommodate the structure of the original graph. If `cycle` is ``False``, + the biggest weight edge is removed to make a Hamiltonian path. + Then each edge on the new complete graph used for that analysis is + replaced by the shortest_path between those nodes on the original graph. + If the input graph `G` includes edges with weights that do not adhere to + the triangle inequality, such as when `G` is not a complete graph (i.e + length of non-existent edges is infinity), then the returned path may + contain some repeating nodes (other than the starting node). + + Parameters + ---------- + G : NetworkX graph + A possibly weighted graph + + nodes : collection of nodes (default=G.nodes) + collection (list, set, etc.) of nodes to visit + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + cycle : bool (default: True) + Indicates whether a cycle should be returned, or a path. + Note: the cycle is the approximate minimal cycle. + The path simply removes the biggest edge in that cycle. + + method : function (default: None) + A function that returns a cycle on all nodes and approximates + the solution to the traveling salesman problem on a complete + graph. The returned cycle is then used to find a corresponding + solution on `G`. `method` should be callable; take inputs + `G`, and `weight`; and return a list of nodes along the cycle. + + Provided options include :func:`christofides`, :func:`greedy_tsp`, + :func:`simulated_annealing_tsp` and :func:`threshold_accepting_tsp`. + + If `method is None`: use :func:`christofides` for undirected `G` and + :func:`asadpour_atsp` for directed `G`. + + **kwargs : dict + Other keyword arguments to be passed to the `method` function passed in. + + Returns + ------- + list + List of nodes in `G` along a path with an approximation of the minimal + path through `nodes`. + + Raises + ------ + NetworkXError + If `G` is a directed graph it has to be strongly connected or the + complete version cannot be generated. + + Examples + -------- + >>> tsp = nx.approximation.traveling_salesman_problem + >>> G = nx.cycle_graph(9) + >>> G[4][5]["weight"] = 5 # all other weights are 1 + >>> tsp(G, nodes=[3, 6]) + [3, 2, 1, 0, 8, 7, 6, 7, 8, 0, 1, 2, 3] + >>> path = tsp(G, cycle=False) + >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4]) + True + + While no longer required, you can still build (curry) your own function + to provide parameter values to the methods. + + >>> SA_tsp = nx.approximation.simulated_annealing_tsp + >>> method = lambda G, weight: SA_tsp(G, "greedy", weight=weight, temp=500) + >>> path = tsp(G, cycle=False, method=method) + >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4]) + True + + Otherwise, pass other keyword arguments directly into the tsp function. + + >>> path = tsp( + ... G, + ... cycle=False, + ... method=nx.approximation.simulated_annealing_tsp, + ... init_cycle="greedy", + ... temp=500, + ... ) + >>> path in ([4, 3, 2, 1, 0, 8, 7, 6, 5], [5, 6, 7, 8, 0, 1, 2, 3, 4]) + True + """ + if method is None: + if G.is_directed(): + method = asadpour_atsp + else: + method = christofides + if nodes is None: + nodes = list(G.nodes) + + dist = {} + path = {} + for n, (d, p) in nx.all_pairs_dijkstra(G, weight=weight): + dist[n] = d + path[n] = p + + if G.is_directed(): + # If the graph is not strongly connected, raise an exception + if not nx.is_strongly_connected(G): + raise nx.NetworkXError("G is not strongly connected") + GG = nx.DiGraph() + else: + GG = nx.Graph() + for u in nodes: + for v in nodes: + if u == v: + continue + # Ensure that the weight attribute on GG has the + # same name as the input graph + GG.add_edge(u, v, **{weight: dist[u][v]}) + + best_GG = method(GG, weight=weight, **kwargs) + + if not cycle: + # find and remove the biggest edge + (u, v) = max(pairwise(best_GG), key=lambda x: dist[x[0]][x[1]]) + pos = best_GG.index(u) + 1 + while best_GG[pos] != v: + pos = best_GG[pos:].index(u) + 1 + best_GG = best_GG[pos:-1] + best_GG[:pos] + + best_path = [] + for u, v in pairwise(best_GG): + best_path.extend(path[u][v][:-1]) + best_path.append(v) + return best_path + + +@not_implemented_for("undirected") +@py_random_state(2) +@nx._dispatchable(edge_attrs="weight", mutates_input=True) +def asadpour_atsp(G, weight="weight", seed=None, source=None): + """ + Returns an approximate solution to the traveling salesman problem. + + This approximate solution is one of the best known approximations for the + asymmetric traveling salesman problem developed by Asadpour et al, + [1]_. The algorithm first solves the Held-Karp relaxation to find a lower + bound for the weight of the cycle. Next, it constructs an exponential + distribution of undirected spanning trees where the probability of an + edge being in the tree corresponds to the weight of that edge using a + maximum entropy rounding scheme. Next we sample that distribution + $2 \\lceil \\ln n \\rceil$ times and save the minimum sampled tree once the + direction of the arcs is added back to the edges. Finally, we augment + then short circuit that graph to find the approximate tour for the + salesman. + + Parameters + ---------- + G : nx.DiGraph + The graph should be a complete weighted directed graph. The + distance between all paris of nodes should be included and the triangle + inequality should hold. That is, the direct edge between any two nodes + should be the path of least cost. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + source : node label (default=`None`) + If given, return the cycle starting and ending at the given node. + + Returns + ------- + cycle : list of nodes + Returns the cycle (list of nodes) that a salesman can follow to minimize + the total weight of the trip. + + Raises + ------ + NetworkXError + If `G` is not complete or has less than two nodes, the algorithm raises + an exception. + + NetworkXError + If `source` is not `None` and is not a node in `G`, the algorithm raises + an exception. + + NetworkXNotImplemented + If `G` is an undirected graph. + + References + ---------- + .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi, + An o(log n/log log n)-approximation algorithm for the asymmetric + traveling salesman problem, Operations research, 65 (2017), + pp. 1043–1061 + + Examples + -------- + >>> import networkx as nx + >>> import networkx.algorithms.approximation as approx + >>> G = nx.complete_graph(3, create_using=nx.DiGraph) + >>> nx.set_edge_attributes( + ... G, + ... {(0, 1): 2, (1, 2): 2, (2, 0): 2, (0, 2): 1, (2, 1): 1, (1, 0): 1}, + ... "weight", + ... ) + >>> tour = approx.asadpour_atsp(G, source=0) + >>> tour + [0, 2, 1, 0] + """ + from math import ceil, exp + from math import log as ln + + # Check that G is a complete graph + N = len(G) - 1 + if N < 1: + raise nx.NetworkXError("G must have at least two nodes") + # This check ignores selfloops which is what we want here. + if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()): + raise nx.NetworkXError("G is not a complete DiGraph") + # Check that the source vertex, if given, is in the graph + if source is not None and source not in G.nodes: + raise nx.NetworkXError("Given source node not in G.") + # handle 2 node case + if N == 1: + if source is None: + return list(G) + return [source, next(n for n in G if n != source)] + + opt_hk, z_star = held_karp_ascent(G, weight) + + # Test to see if the ascent method found an integer solution or a fractional + # solution. If it is integral then z_star is a nx.Graph, otherwise it is + # a dict + if not isinstance(z_star, dict): + # Here we are using the shortcutting method to go from the list of edges + # returned from eulerian_circuit to a list of nodes + return _shortcutting(nx.eulerian_circuit(z_star, source=source)) + + # Create the undirected support of z_star + z_support = nx.MultiGraph() + for u, v in z_star: + if (u, v) not in z_support.edges: + edge_weight = min(G[u][v][weight], G[v][u][weight]) + z_support.add_edge(u, v, **{weight: edge_weight}) + + # Create the exponential distribution of spanning trees + gamma = spanning_tree_distribution(z_support, z_star) + + # Write the lambda values to the edges of z_support + z_support = nx.Graph(z_support) + lambda_dict = {(u, v): exp(gamma[(u, v)]) for u, v in z_support.edges()} + nx.set_edge_attributes(z_support, lambda_dict, "weight") + del gamma, lambda_dict + + # Sample 2 * ceil( ln(n) ) spanning trees and record the minimum one + minimum_sampled_tree = None + minimum_sampled_tree_weight = math.inf + for _ in range(2 * ceil(ln(G.number_of_nodes()))): + sampled_tree = random_spanning_tree(z_support, "weight", seed=seed) + sampled_tree_weight = sampled_tree.size(weight) + if sampled_tree_weight < minimum_sampled_tree_weight: + minimum_sampled_tree = sampled_tree.copy() + minimum_sampled_tree_weight = sampled_tree_weight + + # Orient the edges in that tree to keep the cost of the tree the same. + t_star = nx.MultiDiGraph() + for u, v, d in minimum_sampled_tree.edges(data=weight): + if d == G[u][v][weight]: + t_star.add_edge(u, v, **{weight: d}) + else: + t_star.add_edge(v, u, **{weight: d}) + + # Find the node demands needed to neutralize the flow of t_star in G + node_demands = {n: t_star.out_degree(n) - t_star.in_degree(n) for n in t_star} + nx.set_node_attributes(G, node_demands, "demand") + + # Find the min_cost_flow + flow_dict = nx.min_cost_flow(G, "demand") + + # Build the flow into t_star + for source, values in flow_dict.items(): + for target in values: + if (source, target) not in t_star.edges and values[target] > 0: + # IF values[target] > 0 we have to add that many edges + for _ in range(values[target]): + t_star.add_edge(source, target) + + # Return the shortcut eulerian circuit + circuit = nx.eulerian_circuit(t_star, source=source) + return _shortcutting(circuit) + + +@nx._dispatchable(edge_attrs="weight", mutates_input=True, returns_graph=True) +def held_karp_ascent(G, weight="weight"): + """ + Minimizes the Held-Karp relaxation of the TSP for `G` + + Solves the Held-Karp relaxation of the input complete digraph and scales + the output solution for use in the Asadpour [1]_ ASTP algorithm. + + The Held-Karp relaxation defines the lower bound for solutions to the + ATSP, although it does return a fractional solution. This is used in the + Asadpour algorithm as an initial solution which is later rounded to a + integral tree within the spanning tree polytopes. This function solves + the relaxation with the branch and bound method in [2]_. + + Parameters + ---------- + G : nx.DiGraph + The graph should be a complete weighted directed graph. + The distance between all paris of nodes should be included. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + Returns + ------- + OPT : float + The cost for the optimal solution to the Held-Karp relaxation + z : dict or nx.Graph + A symmetrized and scaled version of the optimal solution to the + Held-Karp relaxation for use in the Asadpour algorithm. + + If an integral solution is found, then that is an optimal solution for + the ATSP problem and that is returned instead. + + References + ---------- + .. [1] A. Asadpour, M. X. Goemans, A. Madry, S. O. Gharan, and A. Saberi, + An o(log n/log log n)-approximation algorithm for the asymmetric + traveling salesman problem, Operations research, 65 (2017), + pp. 1043–1061 + + .. [2] M. Held, R. M. Karp, The traveling-salesman problem and minimum + spanning trees, Operations Research, 1970-11-01, Vol. 18 (6), + pp.1138-1162 + """ + import numpy as np + import scipy as sp + + def k_pi(): + """ + Find the set of minimum 1-Arborescences for G at point pi. + + Returns + ------- + Set + The set of minimum 1-Arborescences + """ + # Create a copy of G without vertex 1. + G_1 = G.copy() + minimum_1_arborescences = set() + minimum_1_arborescence_weight = math.inf + + # node is node '1' in the Held and Karp paper + n = next(G.__iter__()) + G_1.remove_node(n) + + # Iterate over the spanning arborescences of the graph until we know + # that we have found the minimum 1-arborescences. My proposed strategy + # is to find the most extensive root to connect to from 'node 1' and + # the least expensive one. We then iterate over arborescences until + # the cost of the basic arborescence is the cost of the minimum one + # plus the difference between the most and least expensive roots, + # that way the cost of connecting 'node 1' will by definition not by + # minimum + min_root = {"node": None, weight: math.inf} + max_root = {"node": None, weight: -math.inf} + for u, v, d in G.edges(n, data=True): + if d[weight] < min_root[weight]: + min_root = {"node": v, weight: d[weight]} + if d[weight] > max_root[weight]: + max_root = {"node": v, weight: d[weight]} + + min_in_edge = min(G.in_edges(n, data=True), key=lambda x: x[2][weight]) + min_root[weight] = min_root[weight] + min_in_edge[2][weight] + max_root[weight] = max_root[weight] + min_in_edge[2][weight] + + min_arb_weight = math.inf + for arb in nx.ArborescenceIterator(G_1): + arb_weight = arb.size(weight) + if min_arb_weight == math.inf: + min_arb_weight = arb_weight + elif arb_weight > min_arb_weight + max_root[weight] - min_root[weight]: + break + # We have to pick the root node of the arborescence for the out + # edge of the first vertex as that is the only node without an + # edge directed into it. + for N, deg in arb.in_degree: + if deg == 0: + # root found + arb.add_edge(n, N, **{weight: G[n][N][weight]}) + arb_weight += G[n][N][weight] + break + + # We can pick the minimum weight in-edge for the vertex with + # a cycle. If there are multiple edges with the same, minimum + # weight, We need to add all of them. + # + # Delete the edge (N, v) so that we cannot pick it. + edge_data = G[N][n] + G.remove_edge(N, n) + min_weight = min(G.in_edges(n, data=weight), key=lambda x: x[2])[2] + min_edges = [ + (u, v, d) for u, v, d in G.in_edges(n, data=weight) if d == min_weight + ] + for u, v, d in min_edges: + new_arb = arb.copy() + new_arb.add_edge(u, v, **{weight: d}) + new_arb_weight = arb_weight + d + # Check to see the weight of the arborescence, if it is a + # new minimum, clear all of the old potential minimum + # 1-arborescences and add this is the only one. If its + # weight is above the known minimum, do not add it. + if new_arb_weight < minimum_1_arborescence_weight: + minimum_1_arborescences.clear() + minimum_1_arborescence_weight = new_arb_weight + # We have a 1-arborescence, add it to the set + if new_arb_weight == minimum_1_arborescence_weight: + minimum_1_arborescences.add(new_arb) + G.add_edge(N, n, **edge_data) + + return minimum_1_arborescences + + def direction_of_ascent(): + """ + Find the direction of ascent at point pi. + + See [1]_ for more information. + + Returns + ------- + dict + A mapping from the nodes of the graph which represents the direction + of ascent. + + References + ---------- + .. [1] M. Held, R. M. Karp, The traveling-salesman problem and minimum + spanning trees, Operations Research, 1970-11-01, Vol. 18 (6), + pp.1138-1162 + """ + # 1. Set d equal to the zero n-vector. + d = {} + for n in G: + d[n] = 0 + del n + # 2. Find a 1-Arborescence T^k such that k is in K(pi, d). + minimum_1_arborescences = k_pi() + while True: + # Reduce K(pi) to K(pi, d) + # Find the arborescence in K(pi) which increases the lest in + # direction d + min_k_d_weight = math.inf + min_k_d = None + for arborescence in minimum_1_arborescences: + weighted_cost = 0 + for n, deg in arborescence.degree: + weighted_cost += d[n] * (deg - 2) + if weighted_cost < min_k_d_weight: + min_k_d_weight = weighted_cost + min_k_d = arborescence + + # 3. If sum of d_i * v_{i, k} is greater than zero, terminate + if min_k_d_weight > 0: + return d, min_k_d + # 4. d_i = d_i + v_{i, k} + for n, deg in min_k_d.degree: + d[n] += deg - 2 + # Check that we do not need to terminate because the direction + # of ascent does not exist. This is done with linear + # programming. + c = np.full(len(minimum_1_arborescences), -1, dtype=int) + a_eq = np.empty((len(G) + 1, len(minimum_1_arborescences)), dtype=int) + b_eq = np.zeros(len(G) + 1, dtype=int) + b_eq[len(G)] = 1 + for arb_count, arborescence in enumerate(minimum_1_arborescences): + n_count = len(G) - 1 + for n, deg in arborescence.degree: + a_eq[n_count][arb_count] = deg - 2 + n_count -= 1 + a_eq[len(G)][arb_count] = 1 + program_result = sp.optimize.linprog( + c, A_eq=a_eq, b_eq=b_eq, method="highs-ipm" + ) + # If the constants exist, then the direction of ascent doesn't + if program_result.success: + # There is no direction of ascent + return None, minimum_1_arborescences + + # 5. GO TO 2 + + def find_epsilon(k, d): + """ + Given the direction of ascent at pi, find the maximum distance we can go + in that direction. + + Parameters + ---------- + k_xy : set + The set of 1-arborescences which have the minimum rate of increase + in the direction of ascent + + d : dict + The direction of ascent + + Returns + ------- + float + The distance we can travel in direction `d` + """ + min_epsilon = math.inf + for e_u, e_v, e_w in G.edges(data=weight): + if (e_u, e_v) in k.edges: + continue + # Now, I have found a condition which MUST be true for the edges to + # be a valid substitute. The edge in the graph which is the + # substitute is the one with the same terminated end. This can be + # checked rather simply. + # + # Find the edge within k which is the substitute. Because k is a + # 1-arborescence, we know that they is only one such edges + # leading into every vertex. + if len(k.in_edges(e_v, data=weight)) > 1: + raise Exception + sub_u, sub_v, sub_w = next(k.in_edges(e_v, data=weight).__iter__()) + k.add_edge(e_u, e_v, **{weight: e_w}) + k.remove_edge(sub_u, sub_v) + if ( + max(d for n, d in k.in_degree()) <= 1 + and len(G) == k.number_of_edges() + and nx.is_weakly_connected(k) + ): + # Ascent method calculation + if d[sub_u] == d[e_u] or sub_w == e_w: + # Revert to the original graph + k.remove_edge(e_u, e_v) + k.add_edge(sub_u, sub_v, **{weight: sub_w}) + continue + epsilon = (sub_w - e_w) / (d[e_u] - d[sub_u]) + if 0 < epsilon < min_epsilon: + min_epsilon = epsilon + # Revert to the original graph + k.remove_edge(e_u, e_v) + k.add_edge(sub_u, sub_v, **{weight: sub_w}) + + return min_epsilon + + # I have to know that the elements in pi correspond to the correct elements + # in the direction of ascent, even if the node labels are not integers. + # Thus, I will use dictionaries to made that mapping. + pi_dict = {} + for n in G: + pi_dict[n] = 0 + del n + original_edge_weights = {} + for u, v, d in G.edges(data=True): + original_edge_weights[(u, v)] = d[weight] + dir_ascent, k_d = direction_of_ascent() + while dir_ascent is not None: + max_distance = find_epsilon(k_d, dir_ascent) + for n, v in dir_ascent.items(): + pi_dict[n] += max_distance * v + for u, v, d in G.edges(data=True): + d[weight] = original_edge_weights[(u, v)] + pi_dict[u] + dir_ascent, k_d = direction_of_ascent() + nx._clear_cache(G) + # k_d is no longer an individual 1-arborescence but rather a set of + # minimal 1-arborescences at the maximum point of the polytope and should + # be reflected as such + k_max = k_d + + # Search for a cycle within k_max. If a cycle exists, return it as the + # solution + for k in k_max: + if len([n for n in k if k.degree(n) == 2]) == G.order(): + # Tour found + # TODO: this branch does not restore original_edge_weights of G! + return k.size(weight), k + + # Write the original edge weights back to G and every member of k_max at + # the maximum point. Also average the number of times that edge appears in + # the set of minimal 1-arborescences. + x_star = {} + size_k_max = len(k_max) + for u, v, d in G.edges(data=True): + edge_count = 0 + d[weight] = original_edge_weights[(u, v)] + for k in k_max: + if (u, v) in k.edges(): + edge_count += 1 + k[u][v][weight] = original_edge_weights[(u, v)] + x_star[(u, v)] = edge_count / size_k_max + # Now symmetrize the edges in x_star and scale them according to (5) in + # reference [1] + z_star = {} + scale_factor = (G.order() - 1) / G.order() + for u, v in x_star: + frequency = x_star[(u, v)] + x_star[(v, u)] + if frequency > 0: + z_star[(u, v)] = scale_factor * frequency + del x_star + # Return the optimal weight and the z dict + return next(k_max.__iter__()).size(weight), z_star + + +@nx._dispatchable +def spanning_tree_distribution(G, z): + """ + Find the asadpour exponential distribution of spanning trees. + + Solves the Maximum Entropy Convex Program in the Asadpour algorithm [1]_ + using the approach in section 7 to build an exponential distribution of + undirected spanning trees. + + This algorithm ensures that the probability of any edge in a spanning + tree is proportional to the sum of the probabilities of the tress + containing that edge over the sum of the probabilities of all spanning + trees of the graph. + + Parameters + ---------- + G : nx.MultiGraph + The undirected support graph for the Held Karp relaxation + + z : dict + The output of `held_karp_ascent()`, a scaled version of the Held-Karp + solution. + + Returns + ------- + gamma : dict + The probability distribution which approximately preserves the marginal + probabilities of `z`. + """ + from math import exp + from math import log as ln + + def q(e): + """ + The value of q(e) is described in the Asadpour paper is "the + probability that edge e will be included in a spanning tree T that is + chosen with probability proportional to exp(gamma(T))" which + basically means that it is the total probability of the edge appearing + across the whole distribution. + + Parameters + ---------- + e : tuple + The `(u, v)` tuple describing the edge we are interested in + + Returns + ------- + float + The probability that a spanning tree chosen according to the + current values of gamma will include edge `e`. + """ + # Create the laplacian matrices + for u, v, d in G.edges(data=True): + d[lambda_key] = exp(gamma[(u, v)]) + G_Kirchhoff = nx.number_of_spanning_trees(G, weight=lambda_key) + G_e = nx.contracted_edge(G, e, self_loops=False) + G_e_Kirchhoff = nx.number_of_spanning_trees(G_e, weight=lambda_key) + + # Multiply by the weight of the contracted edge since it is not included + # in the total weight of the contracted graph. + return exp(gamma[(e[0], e[1])]) * G_e_Kirchhoff / G_Kirchhoff + + # initialize gamma to the zero dict + gamma = {} + for u, v, _ in G.edges: + gamma[(u, v)] = 0 + + # set epsilon + EPSILON = 0.2 + + # pick an edge attribute name that is unlikely to be in the graph + lambda_key = "spanning_tree_distribution's secret attribute name for lambda" + + while True: + # We need to know that know that no values of q_e are greater than + # (1 + epsilon) * z_e, however changing one gamma value can increase the + # value of a different q_e, so we have to complete the for loop without + # changing anything for the condition to be meet + in_range_count = 0 + # Search for an edge with q_e > (1 + epsilon) * z_e + for u, v in gamma: + e = (u, v) + q_e = q(e) + z_e = z[e] + if q_e > (1 + EPSILON) * z_e: + delta = ln( + (q_e * (1 - (1 + EPSILON / 2) * z_e)) + / ((1 - q_e) * (1 + EPSILON / 2) * z_e) + ) + gamma[e] -= delta + # Check that delta had the desired effect + new_q_e = q(e) + desired_q_e = (1 + EPSILON / 2) * z_e + if round(new_q_e, 8) != round(desired_q_e, 8): + raise nx.NetworkXError( + f"Unable to modify probability for edge ({u}, {v})" + ) + else: + in_range_count += 1 + # Check if the for loop terminated without changing any gamma + if in_range_count == len(gamma): + break + + # Remove the new edge attributes + for _, _, d in G.edges(data=True): + if lambda_key in d: + del d[lambda_key] + + return gamma + + +@nx._dispatchable(edge_attrs="weight") +def greedy_tsp(G, weight="weight", source=None): + """Return a low cost cycle starting at `source` and its cost. + + This approximates a solution to the traveling salesman problem. + It finds a cycle of all the nodes that a salesman can visit in order + to visit many nodes while minimizing total distance. + It uses a simple greedy algorithm. + In essence, this function returns a large cycle given a source point + for which the total cost of the cycle is minimized. + + Parameters + ---------- + G : Graph + The Graph should be a complete weighted undirected graph. + The distance between all pairs of nodes should be included. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + source : node, optional (default: first node in list(G)) + Starting node. If None, defaults to ``next(iter(G))`` + + Returns + ------- + cycle : list of nodes + Returns the cycle (list of nodes) that a salesman + can follow to minimize total weight of the trip. + + Raises + ------ + NetworkXError + If `G` is not complete, the algorithm raises an exception. + + Examples + -------- + >>> from networkx.algorithms import approximation as approx + >>> G = nx.DiGraph() + >>> G.add_weighted_edges_from( + ... { + ... ("A", "B", 3), + ... ("A", "C", 17), + ... ("A", "D", 14), + ... ("B", "A", 3), + ... ("B", "C", 12), + ... ("B", "D", 16), + ... ("C", "A", 13), + ... ("C", "B", 12), + ... ("C", "D", 4), + ... ("D", "A", 14), + ... ("D", "B", 15), + ... ("D", "C", 2), + ... } + ... ) + >>> cycle = approx.greedy_tsp(G, source="D") + >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle)) + >>> cycle + ['D', 'C', 'B', 'A', 'D'] + >>> cost + 31 + + Notes + ----- + This implementation of a greedy algorithm is based on the following: + + - The algorithm adds a node to the solution at every iteration. + - The algorithm selects a node not already in the cycle whose connection + to the previous node adds the least cost to the cycle. + + A greedy algorithm does not always give the best solution. + However, it can construct a first feasible solution which can + be passed as a parameter to an iterative improvement algorithm such + as Simulated Annealing, or Threshold Accepting. + + Time complexity: It has a running time $O(|V|^2)$ + """ + # Check that G is a complete graph + N = len(G) - 1 + # This check ignores selfloops which is what we want here. + if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()): + raise nx.NetworkXError("G must be a complete graph.") + + if source is None: + source = nx.utils.arbitrary_element(G) + + if G.number_of_nodes() == 2: + neighbor = next(G.neighbors(source)) + return [source, neighbor, source] + + nodeset = set(G) + nodeset.remove(source) + cycle = [source] + next_node = source + while nodeset: + nbrdict = G[next_node] + next_node = min(nodeset, key=lambda n: nbrdict[n].get(weight, 1)) + cycle.append(next_node) + nodeset.remove(next_node) + cycle.append(cycle[0]) + return cycle + + +@py_random_state(9) +@nx._dispatchable(edge_attrs="weight") +def simulated_annealing_tsp( + G, + init_cycle, + weight="weight", + source=None, + temp=100, + move="1-1", + max_iterations=10, + N_inner=100, + alpha=0.01, + seed=None, +): + """Returns an approximate solution to the traveling salesman problem. + + This function uses simulated annealing to approximate the minimal cost + cycle through the nodes. Starting from a suboptimal solution, simulated + annealing perturbs that solution, occasionally accepting changes that make + the solution worse to escape from a locally optimal solution. The chance + of accepting such changes decreases over the iterations to encourage + an optimal result. In summary, the function returns a cycle starting + at `source` for which the total cost is minimized. It also returns the cost. + + The chance of accepting a proposed change is related to a parameter called + the temperature (annealing has a physical analogue of steel hardening + as it cools). As the temperature is reduced, the chance of moves that + increase cost goes down. + + Parameters + ---------- + G : Graph + `G` should be a complete weighted graph. + The distance between all pairs of nodes should be included. + + init_cycle : list of all nodes or "greedy" + The initial solution (a cycle through all nodes returning to the start). + This argument has no default to make you think about it. + If "greedy", use `greedy_tsp(G, weight)`. + Other common starting cycles are `list(G) + [next(iter(G))]` or the final + result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + source : node, optional (default: first node in list(G)) + Starting node. If None, defaults to ``next(iter(G))`` + + temp : int, optional (default=100) + The algorithm's temperature parameter. It represents the initial + value of temperature + + move : "1-1" or "1-0" or function, optional (default="1-1") + Indicator of what move to use when finding new trial solutions. + Strings indicate two special built-in moves: + + - "1-1": 1-1 exchange which transposes the position + of two elements of the current solution. + The function called is :func:`swap_two_nodes`. + For example if we apply 1-1 exchange in the solution + ``A = [3, 2, 1, 4, 3]`` + we can get the following by the transposition of 1 and 4 elements: + ``A' = [3, 2, 4, 1, 3]`` + - "1-0": 1-0 exchange which moves an node in the solution + to a new position. + The function called is :func:`move_one_node`. + For example if we apply 1-0 exchange in the solution + ``A = [3, 2, 1, 4, 3]`` + we can transfer the fourth element to the second position: + ``A' = [3, 4, 2, 1, 3]`` + + You may provide your own functions to enact a move from + one solution to a neighbor solution. The function must take + the solution as input along with a `seed` input to control + random number generation (see the `seed` input here). + Your function should maintain the solution as a cycle with + equal first and last node and all others appearing once. + Your function should return the new solution. + + max_iterations : int, optional (default=10) + Declared done when this number of consecutive iterations of + the outer loop occurs without any change in the best cost solution. + + N_inner : int, optional (default=100) + The number of iterations of the inner loop. + + alpha : float between (0, 1), optional (default=0.01) + Percentage of temperature decrease in each iteration + of outer loop + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + cycle : list of nodes + Returns the cycle (list of nodes) that a salesman + can follow to minimize total weight of the trip. + + Raises + ------ + NetworkXError + If `G` is not complete the algorithm raises an exception. + + Examples + -------- + >>> from networkx.algorithms import approximation as approx + >>> G = nx.DiGraph() + >>> G.add_weighted_edges_from( + ... { + ... ("A", "B", 3), + ... ("A", "C", 17), + ... ("A", "D", 14), + ... ("B", "A", 3), + ... ("B", "C", 12), + ... ("B", "D", 16), + ... ("C", "A", 13), + ... ("C", "B", 12), + ... ("C", "D", 4), + ... ("D", "A", 14), + ... ("D", "B", 15), + ... ("D", "C", 2), + ... } + ... ) + >>> cycle = approx.simulated_annealing_tsp(G, "greedy", source="D") + >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle)) + >>> cycle + ['D', 'C', 'B', 'A', 'D'] + >>> cost + 31 + >>> incycle = ["D", "B", "A", "C", "D"] + >>> cycle = approx.simulated_annealing_tsp(G, incycle, source="D") + >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle)) + >>> cycle + ['D', 'C', 'B', 'A', 'D'] + >>> cost + 31 + + Notes + ----- + Simulated Annealing is a metaheuristic local search algorithm. + The main characteristic of this algorithm is that it accepts + even solutions which lead to the increase of the cost in order + to escape from low quality local optimal solutions. + + This algorithm needs an initial solution. If not provided, it is + constructed by a simple greedy algorithm. At every iteration, the + algorithm selects thoughtfully a neighbor solution. + Consider $c(x)$ cost of current solution and $c(x')$ cost of a + neighbor solution. + If $c(x') - c(x) <= 0$ then the neighbor solution becomes the current + solution for the next iteration. Otherwise, the algorithm accepts + the neighbor solution with probability $p = exp - ([c(x') - c(x)] / temp)$. + Otherwise the current solution is retained. + + `temp` is a parameter of the algorithm and represents temperature. + + Time complexity: + For $N_i$ iterations of the inner loop and $N_o$ iterations of the + outer loop, this algorithm has running time $O(N_i * N_o * |V|)$. + + For more information and how the algorithm is inspired see: + http://en.wikipedia.org/wiki/Simulated_annealing + """ + if move == "1-1": + move = swap_two_nodes + elif move == "1-0": + move = move_one_node + if init_cycle == "greedy": + # Construct an initial solution using a greedy algorithm. + cycle = greedy_tsp(G, weight=weight, source=source) + if G.number_of_nodes() == 2: + return cycle + + else: + cycle = list(init_cycle) + if source is None: + source = cycle[0] + elif source != cycle[0]: + raise nx.NetworkXError("source must be first node in init_cycle") + if cycle[0] != cycle[-1]: + raise nx.NetworkXError("init_cycle must be a cycle. (return to start)") + + if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G): + raise nx.NetworkXError("init_cycle should be a cycle over all nodes in G.") + + # Check that G is a complete graph + N = len(G) - 1 + # This check ignores selfloops which is what we want here. + if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()): + raise nx.NetworkXError("G must be a complete graph.") + + if G.number_of_nodes() == 2: + neighbor = next(G.neighbors(source)) + return [source, neighbor, source] + + # Find the cost of initial solution + cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle)) + + count = 0 + best_cycle = cycle.copy() + best_cost = cost + while count <= max_iterations and temp > 0: + count += 1 + for i in range(N_inner): + adj_sol = move(cycle, seed) + adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol)) + delta = adj_cost - cost + if delta <= 0: + # Set current solution the adjacent solution. + cycle = adj_sol + cost = adj_cost + + if cost < best_cost: + count = 0 + best_cycle = cycle.copy() + best_cost = cost + else: + # Accept even a worse solution with probability p. + p = math.exp(-delta / temp) + if p >= seed.random(): + cycle = adj_sol + cost = adj_cost + temp -= temp * alpha + + return best_cycle + + +@py_random_state(9) +@nx._dispatchable(edge_attrs="weight") +def threshold_accepting_tsp( + G, + init_cycle, + weight="weight", + source=None, + threshold=1, + move="1-1", + max_iterations=10, + N_inner=100, + alpha=0.1, + seed=None, +): + """Returns an approximate solution to the traveling salesman problem. + + This function uses threshold accepting methods to approximate the minimal cost + cycle through the nodes. Starting from a suboptimal solution, threshold + accepting methods perturb that solution, accepting any changes that make + the solution no worse than increasing by a threshold amount. Improvements + in cost are accepted, but so are changes leading to small increases in cost. + This allows the solution to leave suboptimal local minima in solution space. + The threshold is decreased slowly as iterations proceed helping to ensure + an optimum. In summary, the function returns a cycle starting at `source` + for which the total cost is minimized. + + Parameters + ---------- + G : Graph + `G` should be a complete weighted graph. + The distance between all pairs of nodes should be included. + + init_cycle : list or "greedy" + The initial solution (a cycle through all nodes returning to the start). + This argument has no default to make you think about it. + If "greedy", use `greedy_tsp(G, weight)`. + Other common starting cycles are `list(G) + [next(iter(G))]` or the final + result of `simulated_annealing_tsp` when doing `threshold_accepting_tsp`. + + weight : string, optional (default="weight") + Edge data key corresponding to the edge weight. + If any edge does not have this attribute the weight is set to 1. + + source : node, optional (default: first node in list(G)) + Starting node. If None, defaults to ``next(iter(G))`` + + threshold : int, optional (default=1) + The algorithm's threshold parameter. It represents the initial + threshold's value + + move : "1-1" or "1-0" or function, optional (default="1-1") + Indicator of what move to use when finding new trial solutions. + Strings indicate two special built-in moves: + + - "1-1": 1-1 exchange which transposes the position + of two elements of the current solution. + The function called is :func:`swap_two_nodes`. + For example if we apply 1-1 exchange in the solution + ``A = [3, 2, 1, 4, 3]`` + we can get the following by the transposition of 1 and 4 elements: + ``A' = [3, 2, 4, 1, 3]`` + - "1-0": 1-0 exchange which moves an node in the solution + to a new position. + The function called is :func:`move_one_node`. + For example if we apply 1-0 exchange in the solution + ``A = [3, 2, 1, 4, 3]`` + we can transfer the fourth element to the second position: + ``A' = [3, 4, 2, 1, 3]`` + + You may provide your own functions to enact a move from + one solution to a neighbor solution. The function must take + the solution as input along with a `seed` input to control + random number generation (see the `seed` input here). + Your function should maintain the solution as a cycle with + equal first and last node and all others appearing once. + Your function should return the new solution. + + max_iterations : int, optional (default=10) + Declared done when this number of consecutive iterations of + the outer loop occurs without any change in the best cost solution. + + N_inner : int, optional (default=100) + The number of iterations of the inner loop. + + alpha : float between (0, 1), optional (default=0.1) + Percentage of threshold decrease when there is at + least one acceptance of a neighbor solution. + If no inner loop moves are accepted the threshold remains unchanged. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + cycle : list of nodes + Returns the cycle (list of nodes) that a salesman + can follow to minimize total weight of the trip. + + Raises + ------ + NetworkXError + If `G` is not complete the algorithm raises an exception. + + Examples + -------- + >>> from networkx.algorithms import approximation as approx + >>> G = nx.DiGraph() + >>> G.add_weighted_edges_from( + ... { + ... ("A", "B", 3), + ... ("A", "C", 17), + ... ("A", "D", 14), + ... ("B", "A", 3), + ... ("B", "C", 12), + ... ("B", "D", 16), + ... ("C", "A", 13), + ... ("C", "B", 12), + ... ("C", "D", 4), + ... ("D", "A", 14), + ... ("D", "B", 15), + ... ("D", "C", 2), + ... } + ... ) + >>> cycle = approx.threshold_accepting_tsp(G, "greedy", source="D") + >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle)) + >>> cycle + ['D', 'C', 'B', 'A', 'D'] + >>> cost + 31 + >>> incycle = ["D", "B", "A", "C", "D"] + >>> cycle = approx.threshold_accepting_tsp(G, incycle, source="D") + >>> cost = sum(G[n][nbr]["weight"] for n, nbr in nx.utils.pairwise(cycle)) + >>> cycle + ['D', 'C', 'B', 'A', 'D'] + >>> cost + 31 + + Notes + ----- + Threshold Accepting is a metaheuristic local search algorithm. + The main characteristic of this algorithm is that it accepts + even solutions which lead to the increase of the cost in order + to escape from low quality local optimal solutions. + + This algorithm needs an initial solution. This solution can be + constructed by a simple greedy algorithm. At every iteration, it + selects thoughtfully a neighbor solution. + Consider $c(x)$ cost of current solution and $c(x')$ cost of + neighbor solution. + If $c(x') - c(x) <= threshold$ then the neighbor solution becomes the current + solution for the next iteration, where the threshold is named threshold. + + In comparison to the Simulated Annealing algorithm, the Threshold + Accepting algorithm does not accept very low quality solutions + (due to the presence of the threshold value). In the case of + Simulated Annealing, even a very low quality solution can + be accepted with probability $p$. + + Time complexity: + It has a running time $O(m * n * |V|)$ where $m$ and $n$ are the number + of times the outer and inner loop run respectively. + + For more information and how algorithm is inspired see: + https://doi.org/10.1016/0021-9991(90)90201-B + + See Also + -------- + simulated_annealing_tsp + + """ + if move == "1-1": + move = swap_two_nodes + elif move == "1-0": + move = move_one_node + if init_cycle == "greedy": + # Construct an initial solution using a greedy algorithm. + cycle = greedy_tsp(G, weight=weight, source=source) + if G.number_of_nodes() == 2: + return cycle + + else: + cycle = list(init_cycle) + if source is None: + source = cycle[0] + elif source != cycle[0]: + raise nx.NetworkXError("source must be first node in init_cycle") + if cycle[0] != cycle[-1]: + raise nx.NetworkXError("init_cycle must be a cycle. (return to start)") + + if len(cycle) - 1 != len(G) or len(set(G.nbunch_iter(cycle))) != len(G): + raise nx.NetworkXError("init_cycle is not all and only nodes.") + + # Check that G is a complete graph + N = len(G) - 1 + # This check ignores selfloops which is what we want here. + if any(len(nbrdict) - (n in nbrdict) != N for n, nbrdict in G.adj.items()): + raise nx.NetworkXError("G must be a complete graph.") + + if G.number_of_nodes() == 2: + neighbor = list(G.neighbors(source))[0] + return [source, neighbor, source] + + # Find the cost of initial solution + cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(cycle)) + + count = 0 + best_cycle = cycle.copy() + best_cost = cost + while count <= max_iterations: + count += 1 + accepted = False + for i in range(N_inner): + adj_sol = move(cycle, seed) + adj_cost = sum(G[u][v].get(weight, 1) for u, v in pairwise(adj_sol)) + delta = adj_cost - cost + if delta <= threshold: + accepted = True + + # Set current solution the adjacent solution. + cycle = adj_sol + cost = adj_cost + + if cost < best_cost: + count = 0 + best_cycle = cycle.copy() + best_cost = cost + if accepted: + threshold -= threshold * alpha + + return best_cycle diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/treewidth.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/treewidth.py new file mode 100644 index 0000000000000000000000000000000000000000..ef1b2f7351bdb1d7815b8218ac1f041acdd55512 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/treewidth.py @@ -0,0 +1,255 @@ +"""Functions for computing treewidth decomposition. + +Treewidth of an undirected graph is a number associated with the graph. +It can be defined as the size of the largest vertex set (bag) in a tree +decomposition of the graph minus one. + +`Wikipedia: Treewidth `_ + +The notions of treewidth and tree decomposition have gained their +attractiveness partly because many graph and network problems that are +intractable (e.g., NP-hard) on arbitrary graphs become efficiently +solvable (e.g., with a linear time algorithm) when the treewidth of the +input graphs is bounded by a constant [1]_ [2]_. + +There are two different functions for computing a tree decomposition: +:func:`treewidth_min_degree` and :func:`treewidth_min_fill_in`. + +.. [1] Hans L. Bodlaender and Arie M. C. A. Koster. 2010. "Treewidth + computations I.Upper bounds". Inf. Comput. 208, 3 (March 2010),259-275. + http://dx.doi.org/10.1016/j.ic.2009.03.008 + +.. [2] Hans L. Bodlaender. "Discovering Treewidth". Institute of Information + and Computing Sciences, Utrecht University. + Technical Report UU-CS-2005-018. + http://www.cs.uu.nl + +.. [3] K. Wang, Z. Lu, and J. Hicks *Treewidth*. + https://web.archive.org/web/20210507025929/http://web.eecs.utk.edu/~cphill25/cs594_spring2015_projects/treewidth.pdf + +""" + +import itertools +import sys +from heapq import heapify, heappop, heappush + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["treewidth_min_degree", "treewidth_min_fill_in"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def treewidth_min_degree(G): + """Returns a treewidth decomposition using the Minimum Degree heuristic. + + The heuristic chooses the nodes according to their degree, i.e., first + the node with the lowest degree is chosen, then the graph is updated + and the corresponding node is removed. Next, a new node with the lowest + degree is chosen, and so on. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + Treewidth decomposition : (int, Graph) tuple + 2-tuple with treewidth and the corresponding decomposed tree. + """ + deg_heuristic = MinDegreeHeuristic(G) + return treewidth_decomp(G, lambda graph: deg_heuristic.best_node(graph)) + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def treewidth_min_fill_in(G): + """Returns a treewidth decomposition using the Minimum Fill-in heuristic. + + The heuristic chooses a node from the graph, where the number of edges + added turning the neighborhood of the chosen node into clique is as + small as possible. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + Treewidth decomposition : (int, Graph) tuple + 2-tuple with treewidth and the corresponding decomposed tree. + """ + return treewidth_decomp(G, min_fill_in_heuristic) + + +class MinDegreeHeuristic: + """Implements the Minimum Degree heuristic. + + The heuristic chooses the nodes according to their degree + (number of neighbors), i.e., first the node with the lowest degree is + chosen, then the graph is updated and the corresponding node is + removed. Next, a new node with the lowest degree is chosen, and so on. + """ + + def __init__(self, graph): + self._graph = graph + + # nodes that have to be updated in the heap before each iteration + self._update_nodes = [] + + self._degreeq = [] # a heapq with 3-tuples (degree,unique_id,node) + self.count = itertools.count() + + # build heap with initial degrees + for n in graph: + self._degreeq.append((len(graph[n]), next(self.count), n)) + heapify(self._degreeq) + + def best_node(self, graph): + # update nodes in self._update_nodes + for n in self._update_nodes: + # insert changed degrees into degreeq + heappush(self._degreeq, (len(graph[n]), next(self.count), n)) + + # get the next valid (minimum degree) node + while self._degreeq: + (min_degree, _, elim_node) = heappop(self._degreeq) + if elim_node not in graph or len(graph[elim_node]) != min_degree: + # outdated entry in degreeq + continue + elif min_degree == len(graph) - 1: + # fully connected: abort condition + return None + + # remember to update nodes in the heap before getting the next node + self._update_nodes = graph[elim_node] + return elim_node + + # the heap is empty: abort + return None + + +def min_fill_in_heuristic(graph_dict): + """Implements the Minimum Degree heuristic. + + graph_dict: dict keyed by node to sets of neighbors (no self-loops) + + Returns the node from the graph, where the number of edges added when + turning the neighborhood of the chosen node into clique is as small as + possible. This algorithm chooses the nodes using the Minimum Fill-In + heuristic. The running time of the algorithm is :math:`O(V^3)` and it uses + additional constant memory. + """ + + if len(graph_dict) == 0: + return None + + min_fill_in_node = None + + min_fill_in = sys.maxsize + + # sort nodes by degree + nodes_by_degree = sorted(graph_dict, key=lambda x: len(graph_dict[x])) + min_degree = len(graph_dict[nodes_by_degree[0]]) + + # abort condition (handle complete graph) + if min_degree == len(graph_dict) - 1: + return None + + for node in nodes_by_degree: + num_fill_in = 0 + nbrs = graph_dict[node] + for nbr in nbrs: + # count how many nodes in nbrs current nbr is not connected to + # subtract 1 for the node itself + num_fill_in += len(nbrs - graph_dict[nbr]) - 1 + if num_fill_in >= 2 * min_fill_in: + break + + num_fill_in /= 2 # divide by 2 because of double counting + + if num_fill_in < min_fill_in: # update min-fill-in node + if num_fill_in == 0: + return node + min_fill_in = num_fill_in + min_fill_in_node = node + + return min_fill_in_node + + +@nx._dispatchable(returns_graph=True) +def treewidth_decomp(G, heuristic=min_fill_in_heuristic): + """Returns a treewidth decomposition using the passed heuristic. + + Parameters + ---------- + G : NetworkX graph + heuristic : heuristic function + + Returns + ------- + Treewidth decomposition : (int, Graph) tuple + 2-tuple with treewidth and the corresponding decomposed tree. + """ + + # make dict-of-sets structure + graph_dict = {n: set(G[n]) - {n} for n in G} + + # stack containing nodes and neighbors in the order from the heuristic + node_stack = [] + + # get first node from heuristic + elim_node = heuristic(graph_dict) + while elim_node is not None: + # connect all neighbors with each other + nbrs = graph_dict[elim_node] + for u, v in itertools.permutations(nbrs, 2): + if v not in graph_dict[u]: + graph_dict[u].add(v) + + # push node and its current neighbors on stack + node_stack.append((elim_node, nbrs)) + + # remove node from graph_dict + for u in graph_dict[elim_node]: + graph_dict[u].remove(elim_node) + + del graph_dict[elim_node] + elim_node = heuristic(graph_dict) + + # the abort condition is met; put all remaining nodes into one bag + decomp = nx.Graph() + first_bag = frozenset(graph_dict.keys()) + decomp.add_node(first_bag) + + treewidth = len(first_bag) - 1 + + while node_stack: + # get node and its neighbors from the stack + (curr_node, nbrs) = node_stack.pop() + + # find a bag all neighbors are in + old_bag = None + for bag in decomp.nodes: + if nbrs <= bag: + old_bag = bag + break + + if old_bag is None: + # no old_bag was found: just connect to the first_bag + old_bag = first_bag + + # create new node for decomposition + nbrs.add(curr_node) + new_bag = frozenset(nbrs) + + # update treewidth + treewidth = max(treewidth, len(new_bag) - 1) + + # add edge to decomposition (implicitly also adds the new node) + decomp.add_edge(old_bag, new_bag) + + return treewidth, decomp diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/vertex_cover.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/vertex_cover.py new file mode 100644 index 0000000000000000000000000000000000000000..13d7167cfc1e4494cbbb2ee8c774e9ffbc3ee495 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/approximation/vertex_cover.py @@ -0,0 +1,83 @@ +"""Functions for computing an approximate minimum weight vertex cover. + +A |vertex cover|_ is a subset of nodes such that each edge in the graph +is incident to at least one node in the subset. + +.. _vertex cover: https://en.wikipedia.org/wiki/Vertex_cover +.. |vertex cover| replace:: *vertex cover* + +""" + +import networkx as nx + +__all__ = ["min_weighted_vertex_cover"] + + +@nx._dispatchable(node_attrs="weight") +def min_weighted_vertex_cover(G, weight=None): + r"""Returns an approximate minimum weighted vertex cover. + + The set of nodes returned by this function is guaranteed to be a + vertex cover, and the total weight of the set is guaranteed to be at + most twice the total weight of the minimum weight vertex cover. In + other words, + + .. math:: + + w(S) \leq 2 * w(S^*), + + where $S$ is the vertex cover returned by this function, + $S^*$ is the vertex cover of minimum weight out of all vertex + covers of the graph, and $w$ is the function that computes the + sum of the weights of each node in that given set. + + Parameters + ---------- + G : NetworkX graph + + weight : string, optional (default = None) + If None, every node has weight 1. If a string, use this node + attribute as the node weight. A node without this attribute is + assumed to have weight 1. + + Returns + ------- + min_weighted_cover : set + Returns a set of nodes whose weight sum is no more than twice + the weight sum of the minimum weight vertex cover. + + Notes + ----- + For a directed graph, a vertex cover has the same definition: a set + of nodes such that each edge in the graph is incident to at least + one node in the set. Whether the node is the head or tail of the + directed edge is ignored. + + This is the local-ratio algorithm for computing an approximate + vertex cover. The algorithm greedily reduces the costs over edges, + iteratively building a cover. The worst-case runtime of this + implementation is $O(m \log n)$, where $n$ is the number + of nodes and $m$ the number of edges in the graph. + + References + ---------- + .. [1] Bar-Yehuda, R., and Even, S. (1985). "A local-ratio theorem for + approximating the weighted vertex cover problem." + *Annals of Discrete Mathematics*, 25, 27–46 + + + """ + cost = dict(G.nodes(data=weight, default=1)) + # While there are uncovered edges, choose an uncovered and update + # the cost of the remaining edges. + cover = set() + for u, v in G.edges(): + if u in cover or v in cover: + continue + if cost[u] <= cost[v]: + cover.add(u) + cost[v] -= cost[u] + else: + cover.add(v) + cost[u] -= cost[v] + return cover diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0bfe50b862831713c105b5824882404173162db Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa89d0ce90dc2bdc033547ba8943113d03a9ae2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..636a6e0db1e0bb2fdc709c22cabe8dd72f16ff56 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/correlation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e6c9f7e614a0640b18340ded3b1e5d34967771e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/mixing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9549b5f497f38b4e9e6c201388c9ff5f8452adfb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/neighbor_degree.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5c088d84a862fb26abe8017cdcb3966596acb0f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/__pycache__/pairs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ee89426e3a1c86505e5a26908dc8c053a29a9e4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..40487a4205d1e86a7678d742ca8105b410a65f4b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/base_test.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dad4add90f3ee6c960629d3442f018259ee9f50 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d8a5ea6651da15241e07411d71af268eb6ca4ea Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_correlation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2243a18d6b462d9fa8edcabd4c59349c90a78bf3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_mixing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca0f860daf5508b39e0bf2758fd20586ab72c769 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_neighbor_degree.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78365bfaea67df5eeec59b25189a96edcb6f3fae Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/__pycache__/test_pairs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py new file mode 100644 index 0000000000000000000000000000000000000000..46d6300649d3b4658a7263cad04354988b4da312 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/base_test.py @@ -0,0 +1,81 @@ +import networkx as nx + + +class BaseTestAttributeMixing: + @classmethod + def setup_class(cls): + G = nx.Graph() + G.add_nodes_from([0, 1], fish="one") + G.add_nodes_from([2, 3], fish="two") + G.add_nodes_from([4], fish="red") + G.add_nodes_from([5], fish="blue") + G.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)]) + cls.G = G + + D = nx.DiGraph() + D.add_nodes_from([0, 1], fish="one") + D.add_nodes_from([2, 3], fish="two") + D.add_nodes_from([4], fish="red") + D.add_nodes_from([5], fish="blue") + D.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)]) + cls.D = D + + M = nx.MultiGraph() + M.add_nodes_from([0, 1], fish="one") + M.add_nodes_from([2, 3], fish="two") + M.add_nodes_from([4], fish="red") + M.add_nodes_from([5], fish="blue") + M.add_edges_from([(0, 1), (0, 1), (2, 3)]) + cls.M = M + + S = nx.Graph() + S.add_nodes_from([0, 1], fish="one") + S.add_nodes_from([2, 3], fish="two") + S.add_nodes_from([4], fish="red") + S.add_nodes_from([5], fish="blue") + S.add_edge(0, 0) + S.add_edge(2, 2) + cls.S = S + + N = nx.Graph() + N.add_nodes_from([0, 1], margin=-2) + N.add_nodes_from([2, 3], margin=-2) + N.add_nodes_from([4], margin=-3) + N.add_nodes_from([5], margin=-4) + N.add_edges_from([(0, 1), (2, 3), (0, 4), (2, 5)]) + cls.N = N + + F = nx.Graph() + F.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5) + F.add_edge(0, 2, weight=1) + nx.set_node_attributes(F, dict(F.degree(weight="weight")), "margin") + cls.F = F + + K = nx.Graph() + K.add_nodes_from([1, 2], margin=-1) + K.add_nodes_from([3], margin=1) + K.add_nodes_from([4], margin=2) + K.add_edges_from([(3, 4), (1, 2), (1, 3)]) + cls.K = K + + +class BaseTestDegreeMixing: + @classmethod + def setup_class(cls): + cls.P4 = nx.path_graph(4) + cls.D = nx.DiGraph() + cls.D.add_edges_from([(0, 2), (0, 3), (1, 3), (2, 3)]) + cls.D2 = nx.DiGraph() + cls.D2.add_edges_from([(0, 3), (1, 0), (1, 2), (2, 4), (4, 1), (4, 3), (4, 2)]) + cls.M = nx.MultiGraph() + nx.add_path(cls.M, range(4)) + cls.M.add_edge(0, 1) + cls.S = nx.Graph() + cls.S.add_edges_from([(0, 0), (1, 1)]) + cls.W = nx.Graph() + cls.W.add_edges_from([(0, 3), (1, 3), (2, 3)], weight=0.5) + cls.W.add_edge(0, 2, weight=1) + S1 = nx.star_graph(4) + S2 = nx.star_graph(4) + cls.DS = nx.disjoint_union(S1, S2) + cls.DS.add_edge(4, 5) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py new file mode 100644 index 0000000000000000000000000000000000000000..21c6287bbe6b0bfc9aa41201b593f342b2d3976e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_connectivity.py @@ -0,0 +1,143 @@ +from itertools import permutations + +import pytest + +import networkx as nx + + +class TestNeighborConnectivity: + def test_degree_p4(self): + G = nx.path_graph(4) + answer = {1: 2.0, 2: 1.5} + nd = nx.average_degree_connectivity(G) + assert nd == answer + + D = G.to_directed() + answer = {2: 2.0, 4: 1.5} + nd = nx.average_degree_connectivity(D) + assert nd == answer + + answer = {1: 2.0, 2: 1.5} + D = G.to_directed() + nd = nx.average_degree_connectivity(D, source="in", target="in") + assert nd == answer + + D = G.to_directed() + nd = nx.average_degree_connectivity(D, source="in", target="in") + assert nd == answer + + def test_degree_p4_weighted(self): + G = nx.path_graph(4) + G[1][2]["weight"] = 4 + answer = {1: 2.0, 2: 1.8} + nd = nx.average_degree_connectivity(G, weight="weight") + assert nd == answer + answer = {1: 2.0, 2: 1.5} + nd = nx.average_degree_connectivity(G) + assert nd == answer + + D = G.to_directed() + answer = {2: 2.0, 4: 1.8} + nd = nx.average_degree_connectivity(D, weight="weight") + assert nd == answer + + answer = {1: 2.0, 2: 1.8} + D = G.to_directed() + nd = nx.average_degree_connectivity( + D, weight="weight", source="in", target="in" + ) + assert nd == answer + + D = G.to_directed() + nd = nx.average_degree_connectivity( + D, source="in", target="out", weight="weight" + ) + assert nd == answer + + def test_weight_keyword(self): + G = nx.path_graph(4) + G[1][2]["other"] = 4 + answer = {1: 2.0, 2: 1.8} + nd = nx.average_degree_connectivity(G, weight="other") + assert nd == answer + answer = {1: 2.0, 2: 1.5} + nd = nx.average_degree_connectivity(G, weight=None) + assert nd == answer + + D = G.to_directed() + answer = {2: 2.0, 4: 1.8} + nd = nx.average_degree_connectivity(D, weight="other") + assert nd == answer + + answer = {1: 2.0, 2: 1.8} + D = G.to_directed() + nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in") + assert nd == answer + + D = G.to_directed() + nd = nx.average_degree_connectivity(D, weight="other", source="in", target="in") + assert nd == answer + + def test_degree_barrat(self): + G = nx.star_graph(5) + G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)]) + G[0][5]["weight"] = 5 + nd = nx.average_degree_connectivity(G)[5] + assert nd == 1.8 + nd = nx.average_degree_connectivity(G, weight="weight")[5] + assert nd == pytest.approx(3.222222, abs=1e-5) + + def test_zero_deg(self): + G = nx.DiGraph() + G.add_edge(1, 2) + G.add_edge(1, 3) + G.add_edge(1, 4) + c = nx.average_degree_connectivity(G) + assert c == {1: 0, 3: 1} + c = nx.average_degree_connectivity(G, source="in", target="in") + assert c == {0: 0, 1: 0} + c = nx.average_degree_connectivity(G, source="in", target="out") + assert c == {0: 0, 1: 3} + c = nx.average_degree_connectivity(G, source="in", target="in+out") + assert c == {0: 0, 1: 3} + c = nx.average_degree_connectivity(G, source="out", target="out") + assert c == {0: 0, 3: 0} + c = nx.average_degree_connectivity(G, source="out", target="in") + assert c == {0: 0, 3: 1} + c = nx.average_degree_connectivity(G, source="out", target="in+out") + assert c == {0: 0, 3: 1} + + def test_in_out_weight(self): + G = nx.DiGraph() + G.add_edge(1, 2, weight=1) + G.add_edge(1, 3, weight=1) + G.add_edge(3, 1, weight=1) + for s, t in permutations(["in", "out", "in+out"], 2): + c = nx.average_degree_connectivity(G, source=s, target=t) + cw = nx.average_degree_connectivity(G, source=s, target=t, weight="weight") + assert c == cw + + def test_invalid_source(self): + with pytest.raises(nx.NetworkXError): + G = nx.DiGraph() + nx.average_degree_connectivity(G, source="bogus") + + def test_invalid_target(self): + with pytest.raises(nx.NetworkXError): + G = nx.DiGraph() + nx.average_degree_connectivity(G, target="bogus") + + def test_invalid_undirected_graph(self): + G = nx.Graph() + with pytest.raises(nx.NetworkXError): + nx.average_degree_connectivity(G, target="bogus") + with pytest.raises(nx.NetworkXError): + nx.average_degree_connectivity(G, source="bogus") + + def test_single_node(self): + # TODO Is this really the intended behavior for providing a + # single node as the argument `nodes`? Shouldn't the function + # just return the connectivity value itself? + G = nx.trivial_graph() + conn = nx.average_degree_connectivity(G, nodes=0) + assert conn == {0: 0} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py new file mode 100644 index 0000000000000000000000000000000000000000..147c837459ae47c4133718615b492d1dee2f0674 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_correlation.py @@ -0,0 +1,122 @@ +import pytest + +import networkx as nx +from networkx.algorithms.assortativity.correlation import attribute_ac + +from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing + +np = pytest.importorskip("numpy") +pytest.importorskip("scipy") + + +class TestDegreeMixingCorrelation(BaseTestDegreeMixing): + def test_degree_assortativity_undirected(self): + r = nx.degree_assortativity_coefficient(self.P4) + np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4) + + def test_degree_assortativity_node_kwargs(self): + G = nx.Graph() + edges = [(0, 1), (0, 3), (1, 2), (1, 3), (1, 4), (5, 9), (9, 0)] + G.add_edges_from(edges) + r = nx.degree_assortativity_coefficient(G, nodes=[1, 2, 4]) + np.testing.assert_almost_equal(r, -1.0, decimal=4) + + def test_degree_assortativity_directed(self): + r = nx.degree_assortativity_coefficient(self.D) + np.testing.assert_almost_equal(r, -0.57735, decimal=4) + + def test_degree_assortativity_directed2(self): + """Test degree assortativity for a directed graph where the set of + in/out degree does not equal the total degree.""" + r = nx.degree_assortativity_coefficient(self.D2) + np.testing.assert_almost_equal(r, 0.14852, decimal=4) + + def test_degree_assortativity_multigraph(self): + r = nx.degree_assortativity_coefficient(self.M) + np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4) + + def test_degree_pearson_assortativity_undirected(self): + r = nx.degree_pearson_correlation_coefficient(self.P4) + np.testing.assert_almost_equal(r, -1.0 / 2, decimal=4) + + def test_degree_pearson_assortativity_directed(self): + r = nx.degree_pearson_correlation_coefficient(self.D) + np.testing.assert_almost_equal(r, -0.57735, decimal=4) + + def test_degree_pearson_assortativity_directed2(self): + """Test degree assortativity with Pearson for a directed graph where + the set of in/out degree does not equal the total degree.""" + r = nx.degree_pearson_correlation_coefficient(self.D2) + np.testing.assert_almost_equal(r, 0.14852, decimal=4) + + def test_degree_pearson_assortativity_multigraph(self): + r = nx.degree_pearson_correlation_coefficient(self.M) + np.testing.assert_almost_equal(r, -1.0 / 7.0, decimal=4) + + def test_degree_assortativity_weighted(self): + r = nx.degree_assortativity_coefficient(self.W, weight="weight") + np.testing.assert_almost_equal(r, -0.1429, decimal=4) + + def test_degree_assortativity_double_star(self): + r = nx.degree_assortativity_coefficient(self.DS) + np.testing.assert_almost_equal(r, -0.9339, decimal=4) + + +class TestAttributeMixingCorrelation(BaseTestAttributeMixing): + def test_attribute_assortativity_undirected(self): + r = nx.attribute_assortativity_coefficient(self.G, "fish") + assert r == 6.0 / 22.0 + + def test_attribute_assortativity_directed(self): + r = nx.attribute_assortativity_coefficient(self.D, "fish") + assert r == 1.0 / 3.0 + + def test_attribute_assortativity_multigraph(self): + r = nx.attribute_assortativity_coefficient(self.M, "fish") + assert r == 1.0 + + def test_attribute_assortativity_coefficient(self): + # from "Mixing patterns in networks" + # fmt: off + a = np.array([[0.258, 0.016, 0.035, 0.013], + [0.012, 0.157, 0.058, 0.019], + [0.013, 0.023, 0.306, 0.035], + [0.005, 0.007, 0.024, 0.016]]) + # fmt: on + r = attribute_ac(a) + np.testing.assert_almost_equal(r, 0.623, decimal=3) + + def test_attribute_assortativity_coefficient2(self): + # fmt: off + a = np.array([[0.18, 0.02, 0.01, 0.03], + [0.02, 0.20, 0.03, 0.02], + [0.01, 0.03, 0.16, 0.01], + [0.03, 0.02, 0.01, 0.22]]) + # fmt: on + r = attribute_ac(a) + np.testing.assert_almost_equal(r, 0.68, decimal=2) + + def test_attribute_assortativity(self): + a = np.array([[50, 50, 0], [50, 50, 0], [0, 0, 2]]) + r = attribute_ac(a) + np.testing.assert_almost_equal(r, 0.029, decimal=3) + + def test_attribute_assortativity_negative(self): + r = nx.numeric_assortativity_coefficient(self.N, "margin") + np.testing.assert_almost_equal(r, -0.2903, decimal=4) + + def test_assortativity_node_kwargs(self): + G = nx.Graph() + G.add_nodes_from([0, 1], size=2) + G.add_nodes_from([2, 3], size=3) + G.add_edges_from([(0, 1), (2, 3)]) + r = nx.numeric_assortativity_coefficient(G, "size", nodes=[0, 3]) + np.testing.assert_almost_equal(r, 1.0, decimal=4) + + def test_attribute_assortativity_float(self): + r = nx.numeric_assortativity_coefficient(self.F, "margin") + np.testing.assert_almost_equal(r, -0.1429, decimal=4) + + def test_attribute_assortativity_mixed(self): + r = nx.numeric_assortativity_coefficient(self.K, "margin") + np.testing.assert_almost_equal(r, 0.4340, decimal=4) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py new file mode 100644 index 0000000000000000000000000000000000000000..589c102b43d4162d1c23cc207dfdbd5319e0e563 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_mixing.py @@ -0,0 +1,174 @@ +import pytest + +import networkx as nx + +from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing + +np = pytest.importorskip("numpy") + + +class TestDegreeMixingDict(BaseTestDegreeMixing): + def test_degree_mixing_dict_undirected(self): + d = nx.degree_mixing_dict(self.P4) + d_result = {1: {2: 2}, 2: {1: 2, 2: 2}} + assert d == d_result + + def test_degree_mixing_dict_undirected_normalized(self): + d = nx.degree_mixing_dict(self.P4, normalized=True) + d_result = {1: {2: 1.0 / 3}, 2: {1: 1.0 / 3, 2: 1.0 / 3}} + assert d == d_result + + def test_degree_mixing_dict_directed(self): + d = nx.degree_mixing_dict(self.D) + d_result = {1: {3: 2}, 2: {1: 1, 3: 1}, 3: {}} + assert d == d_result + + def test_degree_mixing_dict_multigraph(self): + d = nx.degree_mixing_dict(self.M) + d_result = {1: {2: 1}, 2: {1: 1, 3: 3}, 3: {2: 3}} + assert d == d_result + + def test_degree_mixing_dict_weighted(self): + d = nx.degree_mixing_dict(self.W, weight="weight") + d_result = {0.5: {1.5: 1}, 1.5: {1.5: 6, 0.5: 1}} + assert d == d_result + + +class TestDegreeMixingMatrix(BaseTestDegreeMixing): + def test_degree_mixing_matrix_undirected(self): + # fmt: off + a_result = np.array([[0, 2], + [2, 2]] + ) + # fmt: on + a = nx.degree_mixing_matrix(self.P4, normalized=False) + np.testing.assert_equal(a, a_result) + a = nx.degree_mixing_matrix(self.P4) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_degree_mixing_matrix_directed(self): + # fmt: off + a_result = np.array([[0, 0, 2], + [1, 0, 1], + [0, 0, 0]] + ) + # fmt: on + a = nx.degree_mixing_matrix(self.D, normalized=False) + np.testing.assert_equal(a, a_result) + a = nx.degree_mixing_matrix(self.D) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_degree_mixing_matrix_multigraph(self): + # fmt: off + a_result = np.array([[0, 1, 0], + [1, 0, 3], + [0, 3, 0]] + ) + # fmt: on + a = nx.degree_mixing_matrix(self.M, normalized=False) + np.testing.assert_equal(a, a_result) + a = nx.degree_mixing_matrix(self.M) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_degree_mixing_matrix_selfloop(self): + # fmt: off + a_result = np.array([[2]]) + # fmt: on + a = nx.degree_mixing_matrix(self.S, normalized=False) + np.testing.assert_equal(a, a_result) + a = nx.degree_mixing_matrix(self.S) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_degree_mixing_matrix_weighted(self): + a_result = np.array([[0.0, 1.0], [1.0, 6.0]]) + a = nx.degree_mixing_matrix(self.W, weight="weight", normalized=False) + np.testing.assert_equal(a, a_result) + a = nx.degree_mixing_matrix(self.W, weight="weight") + np.testing.assert_equal(a, a_result / float(a_result.sum())) + + def test_degree_mixing_matrix_mapping(self): + a_result = np.array([[6.0, 1.0], [1.0, 0.0]]) + mapping = {0.5: 1, 1.5: 0} + a = nx.degree_mixing_matrix( + self.W, weight="weight", normalized=False, mapping=mapping + ) + np.testing.assert_equal(a, a_result) + + +class TestAttributeMixingDict(BaseTestAttributeMixing): + def test_attribute_mixing_dict_undirected(self): + d = nx.attribute_mixing_dict(self.G, "fish") + d_result = { + "one": {"one": 2, "red": 1}, + "two": {"two": 2, "blue": 1}, + "red": {"one": 1}, + "blue": {"two": 1}, + } + assert d == d_result + + def test_attribute_mixing_dict_directed(self): + d = nx.attribute_mixing_dict(self.D, "fish") + d_result = { + "one": {"one": 1, "red": 1}, + "two": {"two": 1, "blue": 1}, + "red": {}, + "blue": {}, + } + assert d == d_result + + def test_attribute_mixing_dict_multigraph(self): + d = nx.attribute_mixing_dict(self.M, "fish") + d_result = {"one": {"one": 4}, "two": {"two": 2}} + assert d == d_result + + +class TestAttributeMixingMatrix(BaseTestAttributeMixing): + def test_attribute_mixing_matrix_undirected(self): + mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} + a_result = np.array([[2, 0, 1, 0], [0, 2, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]]) + a = nx.attribute_mixing_matrix( + self.G, "fish", mapping=mapping, normalized=False + ) + np.testing.assert_equal(a, a_result) + a = nx.attribute_mixing_matrix(self.G, "fish", mapping=mapping) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_attribute_mixing_matrix_directed(self): + mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} + a_result = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0]]) + a = nx.attribute_mixing_matrix( + self.D, "fish", mapping=mapping, normalized=False + ) + np.testing.assert_equal(a, a_result) + a = nx.attribute_mixing_matrix(self.D, "fish", mapping=mapping) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_attribute_mixing_matrix_multigraph(self): + mapping = {"one": 0, "two": 1, "red": 2, "blue": 3} + a_result = np.array([[4, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) + a = nx.attribute_mixing_matrix( + self.M, "fish", mapping=mapping, normalized=False + ) + np.testing.assert_equal(a, a_result) + a = nx.attribute_mixing_matrix(self.M, "fish", mapping=mapping) + np.testing.assert_equal(a, a_result / a_result.sum()) + + def test_attribute_mixing_matrix_negative(self): + mapping = {-2: 0, -3: 1, -4: 2} + a_result = np.array([[4.0, 1.0, 1.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) + a = nx.attribute_mixing_matrix( + self.N, "margin", mapping=mapping, normalized=False + ) + np.testing.assert_equal(a, a_result) + a = nx.attribute_mixing_matrix(self.N, "margin", mapping=mapping) + np.testing.assert_equal(a, a_result / float(a_result.sum())) + + def test_attribute_mixing_matrix_float(self): + mapping = {0.5: 1, 1.5: 0} + a_result = np.array([[6.0, 1.0], [1.0, 0.0]]) + a = nx.attribute_mixing_matrix( + self.F, "margin", mapping=mapping, normalized=False + ) + np.testing.assert_equal(a, a_result) + a = nx.attribute_mixing_matrix(self.F, "margin", mapping=mapping) + np.testing.assert_equal(a, a_result / a_result.sum()) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py new file mode 100644 index 0000000000000000000000000000000000000000..92421ed4763e0f0fbd7429c523f7610ac35b70ca --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_neighbor_degree.py @@ -0,0 +1,107 @@ +import pytest + +import networkx as nx + + +class TestAverageNeighbor: + def test_degree_p4(self): + G = nx.path_graph(4) + answer = {0: 2, 1: 1.5, 2: 1.5, 3: 2} + nd = nx.average_neighbor_degree(G) + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D) + assert nd == answer + + D = nx.DiGraph(G.edges(data=True)) + nd = nx.average_neighbor_degree(D) + assert nd == {0: 1, 1: 1, 2: 0, 3: 0} + nd = nx.average_neighbor_degree(D, "in", "out") + assert nd == {0: 0, 1: 1, 2: 1, 3: 1} + nd = nx.average_neighbor_degree(D, "out", "in") + assert nd == {0: 1, 1: 1, 2: 1, 3: 0} + nd = nx.average_neighbor_degree(D, "in", "in") + assert nd == {0: 0, 1: 0, 2: 1, 3: 1} + + def test_degree_p4_weighted(self): + G = nx.path_graph(4) + G[1][2]["weight"] = 4 + answer = {0: 2, 1: 1.8, 2: 1.8, 3: 2} + nd = nx.average_neighbor_degree(G, weight="weight") + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D, weight="weight") + assert nd == answer + + D = nx.DiGraph(G.edges(data=True)) + nd = nx.average_neighbor_degree(D, weight="weight") + assert nd == {0: 1, 1: 1, 2: 0, 3: 0} + nd = nx.average_neighbor_degree(D, "out", "out", weight="weight") + assert nd == {0: 1, 1: 1, 2: 0, 3: 0} + nd = nx.average_neighbor_degree(D, "in", "in", weight="weight") + assert nd == {0: 0, 1: 0, 2: 1, 3: 1} + nd = nx.average_neighbor_degree(D, "in", "out", weight="weight") + assert nd == {0: 0, 1: 1, 2: 1, 3: 1} + nd = nx.average_neighbor_degree(D, "out", "in", weight="weight") + assert nd == {0: 1, 1: 1, 2: 1, 3: 0} + nd = nx.average_neighbor_degree(D, source="in+out", weight="weight") + assert nd == {0: 1.0, 1: 1.0, 2: 0.8, 3: 1.0} + nd = nx.average_neighbor_degree(D, target="in+out", weight="weight") + assert nd == {0: 2.0, 1: 2.0, 2: 1.0, 3: 0.0} + + D = G.to_directed() + nd = nx.average_neighbor_degree(D, weight="weight") + assert nd == answer + nd = nx.average_neighbor_degree(D, source="out", target="out", weight="weight") + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D, source="in", target="in", weight="weight") + assert nd == answer + + def test_degree_k4(self): + G = nx.complete_graph(4) + answer = {0: 3, 1: 3, 2: 3, 3: 3} + nd = nx.average_neighbor_degree(G) + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D) + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D) + assert nd == answer + + D = G.to_directed() + nd = nx.average_neighbor_degree(D, source="in", target="in") + assert nd == answer + + def test_degree_k4_nodes(self): + G = nx.complete_graph(4) + answer = {1: 3.0, 2: 3.0} + nd = nx.average_neighbor_degree(G, nodes=[1, 2]) + assert nd == answer + + def test_degree_barrat(self): + G = nx.star_graph(5) + G.add_edges_from([(5, 6), (5, 7), (5, 8), (5, 9)]) + G[0][5]["weight"] = 5 + nd = nx.average_neighbor_degree(G)[5] + assert nd == 1.8 + nd = nx.average_neighbor_degree(G, weight="weight")[5] + assert nd == pytest.approx(3.222222, abs=1e-5) + + def test_error_invalid_source_target(self): + G = nx.path_graph(4) + with pytest.raises(nx.NetworkXError): + nx.average_neighbor_degree(G, "error") + with pytest.raises(nx.NetworkXError): + nx.average_neighbor_degree(G, "in", "error") + G = G.to_directed() + with pytest.raises(nx.NetworkXError): + nx.average_neighbor_degree(G, "error") + with pytest.raises(nx.NetworkXError): + nx.average_neighbor_degree(G, "in", "error") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py new file mode 100644 index 0000000000000000000000000000000000000000..3984292be84dd7b306066809fb3c50a7cf0424f4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/assortativity/tests/test_pairs.py @@ -0,0 +1,87 @@ +import networkx as nx + +from .base_test import BaseTestAttributeMixing, BaseTestDegreeMixing + + +class TestAttributeMixingXY(BaseTestAttributeMixing): + def test_node_attribute_xy_undirected(self): + attrxy = sorted(nx.node_attribute_xy(self.G, "fish")) + attrxy_result = sorted( + [ + ("one", "one"), + ("one", "one"), + ("two", "two"), + ("two", "two"), + ("one", "red"), + ("red", "one"), + ("blue", "two"), + ("two", "blue"), + ] + ) + assert attrxy == attrxy_result + + def test_node_attribute_xy_undirected_nodes(self): + attrxy = sorted(nx.node_attribute_xy(self.G, "fish", nodes=["one", "yellow"])) + attrxy_result = sorted([]) + assert attrxy == attrxy_result + + def test_node_attribute_xy_directed(self): + attrxy = sorted(nx.node_attribute_xy(self.D, "fish")) + attrxy_result = sorted( + [("one", "one"), ("two", "two"), ("one", "red"), ("two", "blue")] + ) + assert attrxy == attrxy_result + + def test_node_attribute_xy_multigraph(self): + attrxy = sorted(nx.node_attribute_xy(self.M, "fish")) + attrxy_result = [ + ("one", "one"), + ("one", "one"), + ("one", "one"), + ("one", "one"), + ("two", "two"), + ("two", "two"), + ] + assert attrxy == attrxy_result + + def test_node_attribute_xy_selfloop(self): + attrxy = sorted(nx.node_attribute_xy(self.S, "fish")) + attrxy_result = [("one", "one"), ("two", "two")] + assert attrxy == attrxy_result + + +class TestDegreeMixingXY(BaseTestDegreeMixing): + def test_node_degree_xy_undirected(self): + xy = sorted(nx.node_degree_xy(self.P4)) + xy_result = sorted([(1, 2), (2, 1), (2, 2), (2, 2), (1, 2), (2, 1)]) + assert xy == xy_result + + def test_node_degree_xy_undirected_nodes(self): + xy = sorted(nx.node_degree_xy(self.P4, nodes=[0, 1, -1])) + xy_result = sorted([(1, 2), (2, 1)]) + assert xy == xy_result + + def test_node_degree_xy_directed(self): + xy = sorted(nx.node_degree_xy(self.D)) + xy_result = sorted([(2, 1), (2, 3), (1, 3), (1, 3)]) + assert xy == xy_result + + def test_node_degree_xy_multigraph(self): + xy = sorted(nx.node_degree_xy(self.M)) + xy_result = sorted( + [(2, 3), (2, 3), (3, 2), (3, 2), (2, 3), (3, 2), (1, 2), (2, 1)] + ) + assert xy == xy_result + + def test_node_degree_xy_selfloop(self): + xy = sorted(nx.node_degree_xy(self.S)) + xy_result = sorted([(2, 2), (2, 2)]) + assert xy == xy_result + + def test_node_degree_xy_weighted(self): + G = nx.Graph() + G.add_edge(1, 2, weight=7) + G.add_edge(2, 3, weight=10) + xy = sorted(nx.node_degree_xy(G, weight="weight")) + xy_result = sorted([(7, 17), (17, 10), (17, 7), (10, 17)]) + assert xy == xy_result diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..edc66b47efa70f9813db54ee3bdc32847aaeff65 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__init__.py @@ -0,0 +1,88 @@ +r"""This module provides functions and operations for bipartite +graphs. Bipartite graphs `B = (U, V, E)` have two node sets `U,V` and edges in +`E` that only connect nodes from opposite sets. It is common in the literature +to use an spatial analogy referring to the two node sets as top and bottom nodes. + +The bipartite algorithms are not imported into the networkx namespace +at the top level so the easiest way to use them is with: + +>>> from networkx.algorithms import bipartite + +NetworkX does not have a custom bipartite graph class but the Graph() +or DiGraph() classes can be used to represent bipartite graphs. However, +you have to keep track of which set each node belongs to, and make +sure that there is no edge between nodes of the same set. The convention used +in NetworkX is to use a node attribute named `bipartite` with values 0 or 1 to +identify the sets each node belongs to. This convention is not enforced in +the source code of bipartite functions, it's only a recommendation. + +For example: + +>>> B = nx.Graph() +>>> # Add nodes with the node attribute "bipartite" +>>> B.add_nodes_from([1, 2, 3, 4], bipartite=0) +>>> B.add_nodes_from(["a", "b", "c"], bipartite=1) +>>> # Add edges only between nodes of opposite node sets +>>> B.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")]) + +Many algorithms of the bipartite module of NetworkX require, as an argument, a +container with all the nodes that belong to one set, in addition to the bipartite +graph `B`. The functions in the bipartite package do not check that the node set +is actually correct nor that the input graph is actually bipartite. +If `B` is connected, you can find the two node sets using a two-coloring +algorithm: + +>>> nx.is_connected(B) +True +>>> bottom_nodes, top_nodes = bipartite.sets(B) + +However, if the input graph is not connected, there are more than one possible +colorations. This is the reason why we require the user to pass a container +with all nodes of one bipartite node set as an argument to most bipartite +functions. In the face of ambiguity, we refuse the temptation to guess and +raise an :exc:`AmbiguousSolution ` +Exception if the input graph for +:func:`bipartite.sets ` +is disconnected. + +Using the `bipartite` node attribute, you can easily get the two node sets: + +>>> top_nodes = {n for n, d in B.nodes(data=True) if d["bipartite"] == 0} +>>> bottom_nodes = set(B) - top_nodes + +So you can easily use the bipartite algorithms that require, as an argument, a +container with all nodes that belong to one node set: + +>>> print(round(bipartite.density(B, bottom_nodes), 2)) +0.5 +>>> G = bipartite.projected_graph(B, top_nodes) + +All bipartite graph generators in NetworkX build bipartite graphs with the +`bipartite` node attribute. Thus, you can use the same approach: + +>>> RB = bipartite.random_graph(5, 7, 0.2) +>>> RB_top = {n for n, d in RB.nodes(data=True) if d["bipartite"] == 0} +>>> RB_bottom = set(RB) - RB_top +>>> list(RB_top) +[0, 1, 2, 3, 4] +>>> list(RB_bottom) +[5, 6, 7, 8, 9, 10, 11] + +For other bipartite graph generators see +:mod:`Generators `. + +""" + +from networkx.algorithms.bipartite.basic import * +from networkx.algorithms.bipartite.centrality import * +from networkx.algorithms.bipartite.cluster import * +from networkx.algorithms.bipartite.covering import * +from networkx.algorithms.bipartite.edgelist import * +from networkx.algorithms.bipartite.matching import * +from networkx.algorithms.bipartite.matrix import * +from networkx.algorithms.bipartite.projection import * +from networkx.algorithms.bipartite.redundancy import * +from networkx.algorithms.bipartite.spectral import * +from networkx.algorithms.bipartite.generators import * +from networkx.algorithms.bipartite.extendability import * +from networkx.algorithms.bipartite.link_analysis import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58738a037a8ca17a09d7cc1b7e2a2e4c228ac8bf Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4ffa533fc0f9c70bc87686b9f30cf867c0c6026 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/basic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..217ea91d1d4b6625ede31726262f4f4ee72cc9dd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e3cf498e5815809f27f3ca42c3f0a4d3f15b36a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/cluster.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b1ec6b786e2212a3482867514c43b36d6273d2a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/covering.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..052bfed40332bbecab98dcdfb0fb4d28faa0a6ce Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/edgelist.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60cc0e754dfa9a4c905af33f87247eca995ff610 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/extendability.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f0537f3a474d21772f5870a6b09b50c3e1a6adf Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/generators.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/link_analysis.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/link_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05ef42f659f0aa4dfa045488069e8f62b0d8c21e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/link_analysis.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f82357e564316798c37e504127cf81f6b2be423 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b60f0a4ecea8e50650a12940e712e6426be0cd8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/matrix.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c4a7556a14a0d60d6390bcc90e8109feb0c270b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/projection.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a97d53453fe0b7c4706a4f7837ebe7ee177c530 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/redundancy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcc49add9141f549d11850446675ce4ed012b8d6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/__pycache__/spectral.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/basic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..8d9a4d5b341bf9a14048acc1132e6f450685cc62 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/basic.py @@ -0,0 +1,322 @@ +""" +========================== +Bipartite Graph Algorithms +========================== +""" + +import networkx as nx +from networkx.algorithms.components import connected_components +from networkx.exception import AmbiguousSolution + +__all__ = [ + "is_bipartite", + "is_bipartite_node_set", + "color", + "sets", + "density", + "degrees", +] + + +@nx._dispatchable +def color(G): + """Returns a two-coloring of the graph. + + Raises an exception if the graph is not bipartite. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + color : dictionary + A dictionary keyed by node with a 1 or 0 as data for each node color. + + Raises + ------ + NetworkXError + If the graph is not two-colorable. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> c = bipartite.color(G) + >>> print(c) + {0: 1, 1: 0, 2: 1, 3: 0} + + You can use this to set a node attribute indicating the bipartite set: + + >>> nx.set_node_attributes(G, c, "bipartite") + >>> print(G.nodes[0]["bipartite"]) + 1 + >>> print(G.nodes[1]["bipartite"]) + 0 + """ + if G.is_directed(): + import itertools + + def neighbors(v): + return itertools.chain.from_iterable([G.predecessors(v), G.successors(v)]) + + else: + neighbors = G.neighbors + + color = {} + for n in G: # handle disconnected graphs + if n in color or len(G[n]) == 0: # skip isolates + continue + queue = [n] + color[n] = 1 # nodes seen with color (1 or 0) + while queue: + v = queue.pop() + c = 1 - color[v] # opposite color of node v + for w in neighbors(v): + if w in color: + if color[w] == color[v]: + raise nx.NetworkXError("Graph is not bipartite.") + else: + color[w] = c + queue.append(w) + # color isolates with 0 + color.update(dict.fromkeys(nx.isolates(G), 0)) + return color + + +@nx._dispatchable +def is_bipartite(G): + """Returns True if graph G is bipartite, False if not. + + Parameters + ---------- + G : NetworkX graph + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> print(bipartite.is_bipartite(G)) + True + + See Also + -------- + color, is_bipartite_node_set + """ + try: + color(G) + return True + except nx.NetworkXError: + return False + + +@nx._dispatchable +def is_bipartite_node_set(G, nodes): + """Returns True if nodes and G/nodes are a bipartition of G. + + Parameters + ---------- + G : NetworkX graph + + nodes: list or container + Check if nodes are a one of a bipartite set. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> X = set([1, 3]) + >>> bipartite.is_bipartite_node_set(G, X) + True + + Notes + ----- + An exception is raised if the input nodes are not distinct, because in this + case some bipartite algorithms will yield incorrect results. + For connected graphs the bipartite sets are unique. This function handles + disconnected graphs. + """ + S = set(nodes) + + if len(S) < len(nodes): + # this should maybe just return False? + raise AmbiguousSolution( + "The input node set contains duplicates.\n" + "This may lead to incorrect results when using it in bipartite algorithms.\n" + "Consider using set(nodes) as the input" + ) + + for CC in (G.subgraph(c).copy() for c in connected_components(G)): + X, Y = sets(CC) + if not ( + (X.issubset(S) and Y.isdisjoint(S)) or (Y.issubset(S) and X.isdisjoint(S)) + ): + return False + return True + + +@nx._dispatchable +def sets(G, top_nodes=None): + """Returns bipartite node sets of graph G. + + Raises an exception if the graph is not bipartite or if the input + graph is disconnected and thus more than one valid solution exists. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + Parameters + ---------- + G : NetworkX graph + + top_nodes : container, optional + Container with all nodes in one bipartite node set. If not supplied + it will be computed. But if more than one solution exists an exception + will be raised. + + Returns + ------- + X : set + Nodes from one side of the bipartite graph. + Y : set + Nodes from the other side. + + Raises + ------ + AmbiguousSolution + Raised if the input bipartite graph is disconnected and no container + with all nodes in one bipartite set is provided. When determining + the nodes in each bipartite set more than one valid solution is + possible if the input graph is disconnected. + NetworkXError + Raised if the input graph is not bipartite. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> X, Y = bipartite.sets(G) + >>> list(X) + [0, 2] + >>> list(Y) + [1, 3] + + See Also + -------- + color + + """ + if G.is_directed(): + is_connected = nx.is_weakly_connected + else: + is_connected = nx.is_connected + if top_nodes is not None: + X = set(top_nodes) + Y = set(G) - X + else: + if not is_connected(G): + msg = "Disconnected graph: Ambiguous solution for bipartite sets." + raise nx.AmbiguousSolution(msg) + c = color(G) + X = {n for n, is_top in c.items() if is_top} + Y = {n for n, is_top in c.items() if not is_top} + return (X, Y) + + +@nx._dispatchable(graphs="B") +def density(B, nodes): + """Returns density of bipartite graph B. + + Parameters + ---------- + B : NetworkX graph + + nodes: list or container + Nodes in one node set of the bipartite graph. + + Returns + ------- + d : float + The bipartite density + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.complete_bipartite_graph(3, 2) + >>> X = set([0, 1, 2]) + >>> bipartite.density(G, X) + 1.0 + >>> Y = set([3, 4]) + >>> bipartite.density(G, Y) + 1.0 + + Notes + ----- + The container of nodes passed as argument must contain all nodes + in one of the two bipartite node sets to avoid ambiguity in the + case of disconnected graphs. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + color + """ + n = len(B) + m = nx.number_of_edges(B) + nb = len(nodes) + nt = n - nb + if m == 0: # includes cases n==0 and n==1 + d = 0.0 + else: + if B.is_directed(): + d = m / (2 * nb * nt) + else: + d = m / (nb * nt) + return d + + +@nx._dispatchable(graphs="B", edge_attrs="weight") +def degrees(B, nodes, weight=None): + """Returns the degrees of the two node sets in the bipartite graph B. + + Parameters + ---------- + B : NetworkX graph + + nodes: list or container + Nodes in one node set of the bipartite graph. + + weight : string or None, optional (default=None) + The edge attribute that holds the numerical value used as a weight. + If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + (degX,degY) : tuple of dictionaries + The degrees of the two bipartite sets as dictionaries keyed by node. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.complete_bipartite_graph(3, 2) + >>> Y = set([3, 4]) + >>> degX, degY = bipartite.degrees(G, Y) + >>> dict(degX) + {0: 2, 1: 2, 2: 2} + + Notes + ----- + The container of nodes passed as argument must contain all nodes + in one of the two bipartite node sets to avoid ambiguity in the + case of disconnected graphs. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + color, density + """ + bottom = set(nodes) + top = set(B) - bottom + return (B.degree(top, weight), B.degree(bottom, weight)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..42d7270ee7d0bb18b56a55dc4c17dc19f5dc77a7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/centrality.py @@ -0,0 +1,290 @@ +import networkx as nx + +__all__ = ["degree_centrality", "betweenness_centrality", "closeness_centrality"] + + +@nx._dispatchable(name="bipartite_degree_centrality") +def degree_centrality(G, nodes): + r"""Compute the degree centrality for nodes in a bipartite network. + + The degree centrality for a node `v` is the fraction of nodes + connected to it. + + Parameters + ---------- + G : graph + A bipartite network + + nodes : list or container + Container with all nodes in one bipartite node set. + + Returns + ------- + centrality : dictionary + Dictionary keyed by node with bipartite degree centrality as the value. + + Examples + -------- + >>> G = nx.wheel_graph(5) + >>> top_nodes = {0, 1, 2} + >>> nx.bipartite.degree_centrality(G, nodes=top_nodes) + {0: 2.0, 1: 1.5, 2: 1.5, 3: 1.0, 4: 1.0} + + See Also + -------- + betweenness_centrality + closeness_centrality + :func:`~networkx.algorithms.bipartite.basic.sets` + :func:`~networkx.algorithms.bipartite.basic.is_bipartite` + + Notes + ----- + The nodes input parameter must contain all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both bipartite node + sets. See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + For unipartite networks, the degree centrality values are + normalized by dividing by the maximum possible degree (which is + `n-1` where `n` is the number of nodes in G). + + In the bipartite case, the maximum possible degree of a node in a + bipartite node set is the number of nodes in the opposite node set + [1]_. The degree centrality for a node `v` in the bipartite + sets `U` with `n` nodes and `V` with `m` nodes is + + .. math:: + + d_{v} = \frac{deg(v)}{m}, \mbox{for} v \in U , + + d_{v} = \frac{deg(v)}{n}, \mbox{for} v \in V , + + + where `deg(v)` is the degree of node `v`. + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + https://dx.doi.org/10.4135/9781446294413.n28 + """ + top = set(nodes) + bottom = set(G) - top + s = 1.0 / len(bottom) + centrality = {n: d * s for n, d in G.degree(top)} + s = 1.0 / len(top) + centrality.update({n: d * s for n, d in G.degree(bottom)}) + return centrality + + +@nx._dispatchable(name="bipartite_betweenness_centrality") +def betweenness_centrality(G, nodes): + r"""Compute betweenness centrality for nodes in a bipartite network. + + Betweenness centrality of a node `v` is the sum of the + fraction of all-pairs shortest paths that pass through `v`. + + Values of betweenness are normalized by the maximum possible + value which for bipartite graphs is limited by the relative size + of the two node sets [1]_. + + Let `n` be the number of nodes in the node set `U` and + `m` be the number of nodes in the node set `V`, then + nodes in `U` are normalized by dividing by + + .. math:: + + \frac{1}{2} [m^2 (s + 1)^2 + m (s + 1)(2t - s - 1) - t (2s - t + 3)] , + + where + + .. math:: + + s = (n - 1) \div m , t = (n - 1) \mod m , + + and nodes in `V` are normalized by dividing by + + .. math:: + + \frac{1}{2} [n^2 (p + 1)^2 + n (p + 1)(2r - p - 1) - r (2p - r + 3)] , + + where, + + .. math:: + + p = (m - 1) \div n , r = (m - 1) \mod n . + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or container + Container with all nodes in one bipartite node set. + + Returns + ------- + betweenness : dictionary + Dictionary keyed by node with bipartite betweenness centrality + as the value. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> top_nodes = {1, 2} + >>> nx.bipartite.betweenness_centrality(G, nodes=top_nodes) + {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} + + See Also + -------- + degree_centrality + closeness_centrality + :func:`~networkx.algorithms.bipartite.basic.sets` + :func:`~networkx.algorithms.bipartite.basic.is_bipartite` + + Notes + ----- + The nodes input parameter must contain all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both node sets. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + https://dx.doi.org/10.4135/9781446294413.n28 + """ + top = set(nodes) + bottom = set(G) - top + n = len(top) + m = len(bottom) + s, t = divmod(n - 1, m) + bet_max_top = ( + ((m**2) * ((s + 1) ** 2)) + + (m * (s + 1) * (2 * t - s - 1)) + - (t * ((2 * s) - t + 3)) + ) / 2.0 + p, r = divmod(m - 1, n) + bet_max_bot = ( + ((n**2) * ((p + 1) ** 2)) + + (n * (p + 1) * (2 * r - p - 1)) + - (r * ((2 * p) - r + 3)) + ) / 2.0 + betweenness = nx.betweenness_centrality(G, normalized=False, weight=None) + for node in top: + betweenness[node] /= bet_max_top + for node in bottom: + betweenness[node] /= bet_max_bot + return betweenness + + +@nx._dispatchable(name="bipartite_closeness_centrality") +def closeness_centrality(G, nodes, normalized=True): + r"""Compute the closeness centrality for nodes in a bipartite network. + + The closeness of a node is the distance to all other nodes in the + graph or in the case that the graph is not connected to all other nodes + in the connected component containing that node. + + Parameters + ---------- + G : graph + A bipartite network + + nodes : list or container + Container with all nodes in one bipartite node set. + + normalized : bool, optional + If True (default) normalize by connected component size. + + Returns + ------- + closeness : dictionary + Dictionary keyed by node with bipartite closeness centrality + as the value. + + Examples + -------- + >>> G = nx.wheel_graph(5) + >>> top_nodes = {0, 1, 2} + >>> nx.bipartite.closeness_centrality(G, nodes=top_nodes) + {0: 1.5, 1: 1.2, 2: 1.2, 3: 1.0, 4: 1.0} + + See Also + -------- + betweenness_centrality + degree_centrality + :func:`~networkx.algorithms.bipartite.basic.sets` + :func:`~networkx.algorithms.bipartite.basic.is_bipartite` + + Notes + ----- + The nodes input parameter must contain all nodes in one bipartite node set, + but the dictionary returned contains all nodes from both node sets. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + + Closeness centrality is normalized by the minimum distance possible. + In the bipartite case the minimum distance for a node in one bipartite + node set is 1 from all nodes in the other node set and 2 from all + other nodes in its own set [1]_. Thus the closeness centrality + for node `v` in the two bipartite sets `U` with + `n` nodes and `V` with `m` nodes is + + .. math:: + + c_{v} = \frac{m + 2(n - 1)}{d}, \mbox{for} v \in U, + + c_{v} = \frac{n + 2(m - 1)}{d}, \mbox{for} v \in V, + + where `d` is the sum of the distances from `v` to all + other nodes. + + Higher values of closeness indicate higher centrality. + + As in the unipartite case, setting normalized=True causes the + values to normalized further to n-1 / size(G)-1 where n is the + number of nodes in the connected part of graph containing the + node. If the graph is not completely connected, this algorithm + computes the closeness centrality for each connected part + separately. + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + https://dx.doi.org/10.4135/9781446294413.n28 + """ + closeness = {} + path_length = nx.single_source_shortest_path_length + top = set(nodes) + bottom = set(G) - top + n = len(top) + m = len(bottom) + for node in top: + sp = dict(path_length(G, node)) + totsp = sum(sp.values()) + if totsp > 0.0 and len(G) > 1: + closeness[node] = (m + 2 * (n - 1)) / totsp + if normalized: + s = (len(sp) - 1) / (len(G) - 1) + closeness[node] *= s + else: + closeness[node] = 0.0 + for node in bottom: + sp = dict(path_length(G, node)) + totsp = sum(sp.values()) + if totsp > 0.0 and len(G) > 1: + closeness[node] = (n + 2 * (m - 1)) / totsp + if normalized: + s = (len(sp) - 1) / (len(G) - 1) + closeness[node] *= s + else: + closeness[node] = 0.0 + return closeness diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/cluster.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/cluster.py new file mode 100644 index 0000000000000000000000000000000000000000..78b3c0f087638483b594f52591363fb03a3bc0a3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/cluster.py @@ -0,0 +1,289 @@ +"""Functions for computing clustering of pairs""" + +import itertools + +import networkx as nx + +__all__ = [ + "clustering", + "average_clustering", + "latapy_clustering", + "robins_alexander_clustering", +] + + +def cc_dot(nu, nv): + return len(nu & nv) / len(nu | nv) + + +def cc_max(nu, nv): + return len(nu & nv) / max(len(nu), len(nv)) + + +def cc_min(nu, nv): + return len(nu & nv) / min(len(nu), len(nv)) + + +modes = {"dot": cc_dot, "min": cc_min, "max": cc_max} + + +@nx._dispatchable +def latapy_clustering(G, nodes=None, mode="dot"): + r"""Compute a bipartite clustering coefficient for nodes. + + The bipartite clustering coefficient is a measure of local density + of connections defined as [1]_: + + .. math:: + + c_u = \frac{\sum_{v \in N(N(u))} c_{uv} }{|N(N(u))|} + + where `N(N(u))` are the second order neighbors of `u` in `G` excluding `u`, + and `c_{uv}` is the pairwise clustering coefficient between nodes + `u` and `v`. + + The mode selects the function for `c_{uv}` which can be: + + `dot`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{|N(u) \cup N(v)|} + + `min`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{min(|N(u)|,|N(v)|)} + + `max`: + + .. math:: + + c_{uv}=\frac{|N(u)\cap N(v)|}{max(|N(u)|,|N(v)|)} + + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or iterable (optional) + Compute bipartite clustering for these nodes. The default + is all nodes in G. + + mode : string + The pairwise bipartite clustering method to be used in the computation. + It must be "dot", "max", or "min". + + Returns + ------- + clustering : dictionary + A dictionary keyed by node with the clustering coefficient value. + + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) # path graphs are bipartite + >>> c = bipartite.clustering(G) + >>> c[0] + 0.5 + >>> c = bipartite.clustering(G, mode="min") + >>> c[0] + 1.0 + + See Also + -------- + robins_alexander_clustering + average_clustering + networkx.algorithms.cluster.square_clustering + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + """ + if not nx.algorithms.bipartite.is_bipartite(G): + raise nx.NetworkXError("Graph is not bipartite") + + try: + cc_func = modes[mode] + except KeyError as err: + raise nx.NetworkXError( + "Mode for bipartite clustering must be: dot, min or max" + ) from err + + if nodes is None: + nodes = G + ccs = {} + for v in nodes: + cc = 0.0 + nbrs2 = {u for nbr in G[v] for u in G[nbr]} - {v} + for u in nbrs2: + cc += cc_func(set(G[u]), set(G[v])) + if cc > 0.0: # len(nbrs2)>0 + cc /= len(nbrs2) + ccs[v] = cc + return ccs + + +clustering = latapy_clustering + + +@nx._dispatchable(name="bipartite_average_clustering") +def average_clustering(G, nodes=None, mode="dot"): + r"""Compute the average bipartite clustering coefficient. + + A clustering coefficient for the whole graph is the average, + + .. math:: + + C = \frac{1}{n}\sum_{v \in G} c_v, + + where `n` is the number of nodes in `G`. + + Similar measures for the two bipartite sets can be defined [1]_ + + .. math:: + + C_X = \frac{1}{|X|}\sum_{v \in X} c_v, + + where `X` is a bipartite set of `G`. + + Parameters + ---------- + G : graph + a bipartite graph + + nodes : list or iterable, optional + A container of nodes to use in computing the average. + The nodes should be either the entire graph (the default) or one of the + bipartite sets. + + mode : string + The pairwise bipartite clustering method. + It must be "dot", "max", or "min" + + Returns + ------- + clustering : float + The average bipartite clustering for the given set of nodes or the + entire graph if no nodes are specified. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.star_graph(3) # star graphs are bipartite + >>> bipartite.average_clustering(G) + 0.75 + >>> X, Y = bipartite.sets(G) + >>> bipartite.average_clustering(G, X) + 0.0 + >>> bipartite.average_clustering(G, Y) + 1.0 + + See Also + -------- + clustering + + Notes + ----- + The container of nodes passed to this function must contain all of the nodes + in one of the bipartite sets ("top" or "bottom") in order to compute + the correct average bipartite clustering coefficients. + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + """ + if nodes is None: + nodes = G + ccs = latapy_clustering(G, nodes=nodes, mode=mode) + return sum(ccs[v] for v in nodes) / len(nodes) + + +@nx._dispatchable +def robins_alexander_clustering(G): + r"""Compute the bipartite clustering of G. + + Robins and Alexander [1]_ defined bipartite clustering coefficient as + four times the number of four cycles `C_4` divided by the number of + three paths `L_3` in a bipartite graph: + + .. math:: + + CC_4 = \frac{4 * C_4}{L_3} + + Parameters + ---------- + G : graph + a bipartite graph + + Returns + ------- + clustering : float + The Robins and Alexander bipartite clustering for the input graph. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.davis_southern_women_graph() + >>> print(round(bipartite.robins_alexander_clustering(G), 3)) + 0.468 + + See Also + -------- + latapy_clustering + networkx.algorithms.cluster.square_clustering + + References + ---------- + .. [1] Robins, G. and M. Alexander (2004). Small worlds among interlocking + directors: Network structure and distance in bipartite graphs. + Computational & Mathematical Organization Theory 10(1), 69–94. + + """ + if G.order() < 4 or G.size() < 3: + return 0 + L_3 = _threepaths(G) + if L_3 == 0: + return 0 + C_4 = _four_cycles(G) + return (4.0 * C_4) / L_3 + + +def _four_cycles(G): + # Also see `square_clustering` which counts squares in a similar way + cycles = 0 + seen = set() + G_adj = G._adj + for v in G: + seen.add(v) + v_neighbors = set(G_adj[v]) + if len(v_neighbors) < 2: + # Can't form a square without at least two neighbors + continue + two_hop_neighbors = set().union(*(G_adj[u] for u in v_neighbors)) + two_hop_neighbors -= seen + for x in two_hop_neighbors: + p2 = len(v_neighbors.intersection(G_adj[x])) + cycles += p2 * (p2 - 1) + return cycles / 4 + + +def _threepaths(G): + paths = 0 + for v in G: + for u in G[v]: + for w in set(G[u]) - {v}: + paths += len(set(G[w]) - {v, u}) + # Divide by two because we count each three path twice + # one for each possible starting point + return paths / 2 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/covering.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/covering.py new file mode 100644 index 0000000000000000000000000000000000000000..f937903e5576ec7313a774863c8470a4a271a252 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/covering.py @@ -0,0 +1,57 @@ +"""Functions related to graph covers.""" + +import networkx as nx +from networkx.algorithms.bipartite.matching import hopcroft_karp_matching +from networkx.algorithms.covering import min_edge_cover as _min_edge_cover +from networkx.utils import not_implemented_for + +__all__ = ["min_edge_cover"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(name="bipartite_min_edge_cover") +def min_edge_cover(G, matching_algorithm=None): + """Returns a set of edges which constitutes + the minimum edge cover of the graph. + + The smallest edge cover can be found in polynomial time by finding + a maximum matching and extending it greedily so that all nodes + are covered. + + Parameters + ---------- + G : NetworkX graph + An undirected bipartite graph. + + matching_algorithm : function + A function that returns a maximum cardinality matching in a + given bipartite graph. The function must take one input, the + graph ``G``, and return a dictionary mapping each node to its + mate. If not specified, + :func:`~networkx.algorithms.bipartite.matching.hopcroft_karp_matching` + will be used. Other possibilities include + :func:`~networkx.algorithms.bipartite.matching.eppstein_matching`, + + Returns + ------- + set + A set of the edges in a minimum edge cover of the graph, given as + pairs of nodes. It contains both the edges `(u, v)` and `(v, u)` + for given nodes `u` and `v` among the edges of minimum edge cover. + + Notes + ----- + An edge cover of a graph is a set of edges such that every node of + the graph is incident to at least one edge of the set. + A minimum edge cover is an edge covering of smallest cardinality. + + Due to its implementation, the worst-case running time of this algorithm + is bounded by the worst-case running time of the function + ``matching_algorithm``. + """ + if G.order() == 0: # Special case for the empty graph + return set() + if matching_algorithm is None: + matching_algorithm = hopcroft_karp_matching + return _min_edge_cover(G, matching_algorithm=matching_algorithm) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/edgelist.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/edgelist.py new file mode 100644 index 0000000000000000000000000000000000000000..c2c6b9c94fa697884546a63d365040db056d233f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/edgelist.py @@ -0,0 +1,360 @@ +""" +******************** +Bipartite Edge Lists +******************** +Read and write NetworkX graphs as bipartite edge lists. + +Format +------ +You can read or write three formats of edge lists with these functions. + +Node pairs with no data:: + + 1 2 + +Python dictionary as data:: + + 1 2 {'weight':7, 'color':'green'} + +Arbitrary data:: + + 1 2 7 green + +For each edge (u, v) the node u is assigned to part 0 and the node v to part 1. +""" + +__all__ = ["generate_edgelist", "write_edgelist", "parse_edgelist", "read_edgelist"] + +import networkx as nx +from networkx.utils import not_implemented_for, open_file + + +@open_file(1, mode="wb") +def write_edgelist(G, path, comments="#", delimiter=" ", data=True, encoding="utf-8"): + """Write a bipartite graph as a list of edges. + + Parameters + ---------- + G : Graph + A NetworkX bipartite graph + path : file or string + File or filename to write. If a file is provided, it must be + opened in 'wb' mode. Filenames ending in .gz or .bz2 will be compressed. + comments : string, optional + The character used to indicate the start of a comment + delimiter : string, optional + The string used to separate values. The default is whitespace. + data : bool or list, optional + If False write no edge data. + If True write a string representation of the edge data dictionary.. + If a list (or other iterable) is provided, write the keys specified + in the list. + encoding: string, optional + Specify which encoding to use when writing file. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> G.add_nodes_from([0, 2], bipartite=0) + >>> G.add_nodes_from([1, 3], bipartite=1) + >>> nx.write_edgelist(G, "test.edgelist") + >>> fh = open("test.edgelist_open", "wb") + >>> nx.write_edgelist(G, fh) + >>> nx.write_edgelist(G, "test.edgelist.gz") + >>> nx.write_edgelist(G, "test.edgelist_nodata.gz", data=False) + + >>> G = nx.Graph() + >>> G.add_edge(1, 2, weight=7, color="red") + >>> nx.write_edgelist(G, "test.edgelist_bigger_nodata", data=False) + >>> nx.write_edgelist(G, "test.edgelist_color", data=["color"]) + >>> nx.write_edgelist(G, "test.edgelist_color_weight", data=["color", "weight"]) + + See Also + -------- + write_edgelist + generate_edgelist + """ + for line in generate_edgelist(G, delimiter, data): + line += "\n" + path.write(line.encode(encoding)) + + +@not_implemented_for("directed") +def generate_edgelist(G, delimiter=" ", data=True): + """Generate a single line of the bipartite graph G in edge list format. + + Parameters + ---------- + G : NetworkX graph + The graph is assumed to have node attribute `part` set to 0,1 representing + the two graph parts + + delimiter : string, optional + Separator for node labels + + data : bool or list of keys + If False generate no edge data. If True use a dictionary + representation of edge data. If a list of keys use a list of data + values corresponding to the keys. + + Returns + ------- + lines : string + Lines of data in adjlist format. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> G.add_nodes_from([0, 2], bipartite=0) + >>> G.add_nodes_from([1, 3], bipartite=1) + >>> G[1][2]["weight"] = 3 + >>> G[2][3]["capacity"] = 12 + >>> for line in bipartite.generate_edgelist(G, data=False): + ... print(line) + 0 1 + 2 1 + 2 3 + + >>> for line in bipartite.generate_edgelist(G): + ... print(line) + 0 1 {} + 2 1 {'weight': 3} + 2 3 {'capacity': 12} + + >>> for line in bipartite.generate_edgelist(G, data=["weight"]): + ... print(line) + 0 1 + 2 1 3 + 2 3 + """ + try: + part0 = [n for n, d in G.nodes.items() if d["bipartite"] == 0] + except BaseException as err: + raise AttributeError("Missing node attribute `bipartite`") from err + if data is True or data is False: + for n in part0: + for edge in G.edges(n, data=data): + yield delimiter.join(map(str, edge)) + else: + for n in part0: + for u, v, d in G.edges(n, data=True): + edge = [u, v] + try: + edge.extend(d[k] for k in data) + except KeyError: + pass # missing data for this edge, should warn? + yield delimiter.join(map(str, edge)) + + +@nx._dispatchable(name="bipartite_parse_edgelist", graphs=None, returns_graph=True) +def parse_edgelist( + lines, comments="#", delimiter=None, create_using=None, nodetype=None, data=True +): + """Parse lines of an edge list representation of a bipartite graph. + + Parameters + ---------- + lines : list or iterator of strings + Input data in edgelist format + comments : string, optional + Marker for comment lines + delimiter : string, optional + Separator for node labels + create_using: NetworkX graph container, optional + Use given NetworkX graph for holding nodes or edges. + nodetype : Python type, optional + Convert nodes to this type. + data : bool or list of (label,type) tuples + If False generate no edge data or if True use a dictionary + representation of edge data or a list tuples specifying dictionary + key names and types for edge data. + + Returns + ------- + G: NetworkX Graph + The bipartite graph corresponding to lines + + Examples + -------- + Edgelist with no data: + + >>> from networkx.algorithms import bipartite + >>> lines = ["1 2", "2 3", "3 4"] + >>> G = bipartite.parse_edgelist(lines, nodetype=int) + >>> sorted(G.nodes()) + [1, 2, 3, 4] + >>> sorted(G.nodes(data=True)) + [(1, {'bipartite': 0}), (2, {'bipartite': 0}), (3, {'bipartite': 0}), (4, {'bipartite': 1})] + >>> sorted(G.edges()) + [(1, 2), (2, 3), (3, 4)] + + Edgelist with data in Python dictionary representation: + + >>> lines = ["1 2 {'weight':3}", "2 3 {'weight':27}", "3 4 {'weight':3.0}"] + >>> G = bipartite.parse_edgelist(lines, nodetype=int) + >>> sorted(G.nodes()) + [1, 2, 3, 4] + >>> sorted(G.edges(data=True)) + [(1, 2, {'weight': 3}), (2, 3, {'weight': 27}), (3, 4, {'weight': 3.0})] + + Edgelist with data in a list: + + >>> lines = ["1 2 3", "2 3 27", "3 4 3.0"] + >>> G = bipartite.parse_edgelist(lines, nodetype=int, data=(("weight", float),)) + >>> sorted(G.nodes()) + [1, 2, 3, 4] + >>> sorted(G.edges(data=True)) + [(1, 2, {'weight': 3.0}), (2, 3, {'weight': 27.0}), (3, 4, {'weight': 3.0})] + + See Also + -------- + """ + from ast import literal_eval + + G = nx.empty_graph(0, create_using) + for line in lines: + p = line.find(comments) + if p >= 0: + line = line[:p] + if not len(line): + continue + # split line, should have 2 or more + s = line.rstrip("\n").split(delimiter) + if len(s) < 2: + continue + u = s.pop(0) + v = s.pop(0) + d = s + if nodetype is not None: + try: + u = nodetype(u) + v = nodetype(v) + except BaseException as err: + raise TypeError( + f"Failed to convert nodes {u},{v} to type {nodetype}." + ) from err + + if len(d) == 0 or data is False: + # no data or data type specified + edgedata = {} + elif data is True: + # no edge types specified + try: # try to evaluate as dictionary + edgedata = dict(literal_eval(" ".join(d))) + except BaseException as err: + raise TypeError( + f"Failed to convert edge data ({d}) to dictionary." + ) from err + else: + # convert edge data to dictionary with specified keys and type + if len(d) != len(data): + raise IndexError( + f"Edge data {d} and data_keys {data} are not the same length" + ) + edgedata = {} + for (edge_key, edge_type), edge_value in zip(data, d): + try: + edge_value = edge_type(edge_value) + except BaseException as err: + raise TypeError( + f"Failed to convert {edge_key} data " + f"{edge_value} to type {edge_type}." + ) from err + edgedata.update({edge_key: edge_value}) + G.add_node(u, bipartite=0) + G.add_node(v, bipartite=1) + G.add_edge(u, v, **edgedata) + return G + + +@open_file(0, mode="rb") +@nx._dispatchable(name="bipartite_read_edgelist", graphs=None, returns_graph=True) +def read_edgelist( + path, + comments="#", + delimiter=None, + create_using=None, + nodetype=None, + data=True, + edgetype=None, + encoding="utf-8", +): + """Read a bipartite graph from a list of edges. + + Parameters + ---------- + path : file or string + File or filename to read. If a file is provided, it must be + opened in 'rb' mode. + Filenames ending in .gz or .bz2 will be decompressed. + comments : string, optional + The character used to indicate the start of a comment. + delimiter : string, optional + The string used to separate values. The default is whitespace. + create_using : Graph container, optional, + Use specified container to build graph. The default is networkx.Graph, + an undirected graph. + nodetype : int, float, str, Python type, optional + Convert node data from strings to specified type + data : bool or list of (label,type) tuples + Tuples specifying dictionary key names and types for edge data + edgetype : int, float, str, Python type, optional OBSOLETE + Convert edge data from strings to specified type and use as 'weight' + encoding: string, optional + Specify which encoding to use when reading file. + + Returns + ------- + G : graph + A networkx Graph or other type specified with create_using + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> G.add_nodes_from([0, 2], bipartite=0) + >>> G.add_nodes_from([1, 3], bipartite=1) + >>> bipartite.write_edgelist(G, "test.edgelist") + >>> G = bipartite.read_edgelist("test.edgelist") + + >>> fh = open("test.edgelist", "rb") + >>> G = bipartite.read_edgelist(fh) + >>> fh.close() + + >>> G = bipartite.read_edgelist("test.edgelist", nodetype=int) + + Edgelist with data in a list: + + >>> textline = "1 2 3" + >>> fh = open("test.edgelist", "w") + >>> d = fh.write(textline) + >>> fh.close() + >>> G = bipartite.read_edgelist( + ... "test.edgelist", nodetype=int, data=(("weight", float),) + ... ) + >>> list(G) + [1, 2] + >>> list(G.edges(data=True)) + [(1, 2, {'weight': 3.0})] + + See parse_edgelist() for more examples of formatting. + + See Also + -------- + parse_edgelist + + Notes + ----- + Since nodes must be hashable, the function nodetype must return hashable + types (e.g. int, float, str, frozenset - or tuples of those, etc.) + """ + lines = (line.decode(encoding) for line in path) + return parse_edgelist( + lines, + comments=comments, + delimiter=delimiter, + create_using=create_using, + nodetype=nodetype, + data=data, + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/extendability.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/extendability.py new file mode 100644 index 0000000000000000000000000000000000000000..61d8d067d9792659ed7097340c5ece28d9dc2e8c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/extendability.py @@ -0,0 +1,105 @@ +"""Provides a function for computing the extendability of a graph which is +undirected, simple, connected and bipartite and contains at least one perfect matching.""" + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["maximal_extendability"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def maximal_extendability(G): + """Computes the extendability of a graph. + + The extendability of a graph is defined as the maximum $k$ for which `G` + is $k$-extendable. Graph `G` is $k$-extendable if and only if `G` has a + perfect matching and every set of $k$ independent edges can be extended + to a perfect matching in `G`. + + Parameters + ---------- + G : NetworkX Graph + A fully-connected bipartite graph without self-loops + + Returns + ------- + extendability : int + + Raises + ------ + NetworkXError + If the graph `G` is disconnected. + If the graph `G` is not bipartite. + If the graph `G` does not contain a perfect matching. + If the residual graph of `G` is not strongly connected. + + Notes + ----- + Definition: + Let `G` be a simple, connected, undirected and bipartite graph with a perfect + matching M and bipartition (U,V). The residual graph of `G`, denoted by $G_M$, + is the graph obtained from G by directing the edges of M from V to U and the + edges that do not belong to M from U to V. + + Lemma [1]_ : + Let M be a perfect matching of `G`. `G` is $k$-extendable if and only if its residual + graph $G_M$ is strongly connected and there are $k$ vertex-disjoint directed + paths between every vertex of U and every vertex of V. + + Assuming that input graph `G` is undirected, simple, connected, bipartite and contains + a perfect matching M, this function constructs the residual graph $G_M$ of G and + returns the minimum value among the maximum vertex-disjoint directed paths between + every vertex of U and every vertex of V in $G_M$. By combining the definitions + and the lemma, this value represents the extendability of the graph `G`. + + Time complexity O($n^3$ $m^2$)) where $n$ is the number of vertices + and $m$ is the number of edges. + + References + ---------- + .. [1] "A polynomial algorithm for the extendability problem in bipartite graphs", + J. Lakhal, L. Litzler, Information Processing Letters, 1998. + .. [2] "On n-extendible graphs", M. D. Plummer, Discrete Mathematics, 31:201–210, 1980 + https://doi.org/10.1016/0012-365X(80)90037-0 + + """ + if not nx.is_connected(G): + raise nx.NetworkXError("Graph G is not connected") + + if not nx.bipartite.is_bipartite(G): + raise nx.NetworkXError("Graph G is not bipartite") + + U, V = nx.bipartite.sets(G) + + maximum_matching = nx.bipartite.hopcroft_karp_matching(G) + + if not nx.is_perfect_matching(G, maximum_matching): + raise nx.NetworkXError("Graph G does not contain a perfect matching") + + # list of edges in perfect matching, directed from V to U + pm = [(node, maximum_matching[node]) for node in V & maximum_matching.keys()] + + # Direct all the edges of G, from V to U if in matching, else from U to V + directed_edges = [ + (x, y) if (x in V and (x, y) in pm) or (x in U and (y, x) not in pm) else (y, x) + for x, y in G.edges + ] + + # Construct the residual graph of G + residual_G = nx.DiGraph() + residual_G.add_nodes_from(G) + residual_G.add_edges_from(directed_edges) + + if not nx.is_strongly_connected(residual_G): + raise nx.NetworkXError("The residual graph of G is not strongly connected") + + # For node-pairs between V & U, keep min of max number of node-disjoint paths + # Variable $k$ stands for the extendability of graph G + k = float("inf") + for u in U: + for v in V: + num_paths = sum(1 for _ in nx.node_disjoint_paths(residual_G, u, v)) + k = k if k < num_paths else num_paths + return k diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/generators.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/generators.py new file mode 100644 index 0000000000000000000000000000000000000000..6e73a4d132d5ab34586f0b5d4aee967b6f77738f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/generators.py @@ -0,0 +1,603 @@ +""" +Generators and functions for bipartite graphs. +""" + +import math +import numbers +from functools import reduce + +import networkx as nx +from networkx.utils import nodes_or_number, py_random_state + +__all__ = [ + "configuration_model", + "havel_hakimi_graph", + "reverse_havel_hakimi_graph", + "alternating_havel_hakimi_graph", + "preferential_attachment_graph", + "random_graph", + "gnmk_random_graph", + "complete_bipartite_graph", +] + + +@nx._dispatchable(graphs=None, returns_graph=True) +@nodes_or_number([0, 1]) +def complete_bipartite_graph(n1, n2, create_using=None): + """Returns the complete bipartite graph `K_{n_1,n_2}`. + + The graph is composed of two partitions with nodes 0 to (n1 - 1) + in the first and nodes n1 to (n1 + n2 - 1) in the second. + Each node in the first is connected to each node in the second. + + Parameters + ---------- + n1, n2 : integer or iterable container of nodes + If integers, nodes are from `range(n1)` and `range(n1, n1 + n2)`. + If a container, the elements are the nodes. + create_using : NetworkX graph instance, (default: nx.Graph) + Return graph of this type. + + Notes + ----- + Nodes are the integers 0 to `n1 + n2 - 1` unless either n1 or n2 are + containers of nodes. If only one of n1 or n2 are integers, that + integer is replaced by `range` of that integer. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.complete_bipartite_graph + """ + G = nx.empty_graph(0, create_using) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + n1, top = n1 + n2, bottom = n2 + if isinstance(n1, numbers.Integral) and isinstance(n2, numbers.Integral): + bottom = [n1 + i for i in bottom] + G.add_nodes_from(top, bipartite=0) + G.add_nodes_from(bottom, bipartite=1) + if len(G) != len(top) + len(bottom): + raise nx.NetworkXError("Inputs n1 and n2 must contain distinct nodes") + G.add_edges_from((u, v) for u in top for v in bottom) + G.graph["name"] = f"complete_bipartite_graph({len(top)}, {len(bottom)})" + return G + + +@py_random_state(3) +@nx._dispatchable(name="bipartite_configuration_model", graphs=None, returns_graph=True) +def configuration_model(aseq, bseq, create_using=None, seed=None): + """Returns a random bipartite graph from two given degree sequences. + + Parameters + ---------- + aseq : list + Degree sequence for node set A. + bseq : list + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + The graph is composed of two partitions. Set A has nodes 0 to + (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). + Nodes from set A are connected to nodes in set B by choosing + randomly from the possible free stubs, one in A and one in B. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.configuration_model + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # length and sum of each sequence + lena = len(aseq) + lenb = len(bseq) + suma = sum(aseq) + sumb = sum(bseq) + + if not suma == sumb: + raise nx.NetworkXError( + f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" + ) + + G = _add_nodes_with_bipartite_label(G, lena, lenb) + + if len(aseq) == 0 or max(aseq) == 0: + return G # done if no edges + + # build lists of degree-repeated vertex numbers + stubs = [[v] * aseq[v] for v in range(lena)] + astubs = [x for subseq in stubs for x in subseq] + + stubs = [[v] * bseq[v - lena] for v in range(lena, lena + lenb)] + bstubs = [x for subseq in stubs for x in subseq] + + # shuffle lists + seed.shuffle(astubs) + seed.shuffle(bstubs) + + G.add_edges_from([astubs[i], bstubs[i]] for i in range(suma)) + + G.name = "bipartite_configuration_model" + return G + + +@nx._dispatchable(name="bipartite_havel_hakimi_graph", graphs=None, returns_graph=True) +def havel_hakimi_graph(aseq, bseq, create_using=None): + """Returns a bipartite graph from two given degree sequences using a + Havel-Hakimi style construction. + + The graph is composed of two partitions. Set A has nodes 0 to + (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). + Nodes from the set A are connected to nodes in the set B by + connecting the highest degree nodes in set A to the highest degree + nodes in set B until all stubs are connected. + + Parameters + ---------- + aseq : list + Degree sequence for node set A. + bseq : list + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.havel_hakimi_graph + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # length of the each sequence + naseq = len(aseq) + nbseq = len(bseq) + + suma = sum(aseq) + sumb = sum(bseq) + + if not suma == sumb: + raise nx.NetworkXError( + f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" + ) + + G = _add_nodes_with_bipartite_label(G, naseq, nbseq) + + if len(aseq) == 0 or max(aseq) == 0: + return G # done if no edges + + # build list of degree-repeated vertex numbers + astubs = [[aseq[v], v] for v in range(naseq)] + bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)] + astubs.sort() + while astubs: + (degree, u) = astubs.pop() # take of largest degree node in the a set + if degree == 0: + break # done, all are zero + # connect the source to largest degree nodes in the b set + bstubs.sort() + for target in bstubs[-degree:]: + v = target[1] + G.add_edge(u, v) + target[0] -= 1 # note this updates bstubs too. + if target[0] == 0: + bstubs.remove(target) + + G.name = "bipartite_havel_hakimi_graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def reverse_havel_hakimi_graph(aseq, bseq, create_using=None): + """Returns a bipartite graph from two given degree sequences using a + Havel-Hakimi style construction. + + The graph is composed of two partitions. Set A has nodes 0 to + (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). + Nodes from set A are connected to nodes in the set B by connecting + the highest degree nodes in set A to the lowest degree nodes in + set B until all stubs are connected. + + Parameters + ---------- + aseq : list + Degree sequence for node set A. + bseq : list + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.reverse_havel_hakimi_graph + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # length of the each sequence + lena = len(aseq) + lenb = len(bseq) + suma = sum(aseq) + sumb = sum(bseq) + + if not suma == sumb: + raise nx.NetworkXError( + f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" + ) + + G = _add_nodes_with_bipartite_label(G, lena, lenb) + + if len(aseq) == 0 or max(aseq) == 0: + return G # done if no edges + + # build list of degree-repeated vertex numbers + astubs = [[aseq[v], v] for v in range(lena)] + bstubs = [[bseq[v - lena], v] for v in range(lena, lena + lenb)] + astubs.sort() + bstubs.sort() + while astubs: + (degree, u) = astubs.pop() # take of largest degree node in the a set + if degree == 0: + break # done, all are zero + # connect the source to the smallest degree nodes in the b set + for target in bstubs[0:degree]: + v = target[1] + G.add_edge(u, v) + target[0] -= 1 # note this updates bstubs too. + if target[0] == 0: + bstubs.remove(target) + + G.name = "bipartite_reverse_havel_hakimi_graph" + return G + + +@nx._dispatchable(graphs=None, returns_graph=True) +def alternating_havel_hakimi_graph(aseq, bseq, create_using=None): + """Returns a bipartite graph from two given degree sequences using + an alternating Havel-Hakimi style construction. + + The graph is composed of two partitions. Set A has nodes 0 to + (len(aseq) - 1) and set B has nodes len(aseq) to (len(bseq) - 1). + Nodes from the set A are connected to nodes in the set B by + connecting the highest degree nodes in set A to alternatively the + highest and the lowest degree nodes in set B until all stubs are + connected. + + Parameters + ---------- + aseq : list + Degree sequence for node set A. + bseq : list + Degree sequence for node set B. + create_using : NetworkX graph instance, optional + Return graph of this type. + + Notes + ----- + The sum of the two sequences must be equal: sum(aseq)=sum(bseq) + If no graph type is specified use MultiGraph with parallel edges. + If you want a graph with no parallel edges use create_using=Graph() + but then the resulting degree sequences might not be exact. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.alternating_havel_hakimi_graph + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + # length of the each sequence + naseq = len(aseq) + nbseq = len(bseq) + suma = sum(aseq) + sumb = sum(bseq) + + if not suma == sumb: + raise nx.NetworkXError( + f"invalid degree sequences, sum(aseq)!=sum(bseq),{suma},{sumb}" + ) + + G = _add_nodes_with_bipartite_label(G, naseq, nbseq) + + if len(aseq) == 0 or max(aseq) == 0: + return G # done if no edges + # build list of degree-repeated vertex numbers + astubs = [[aseq[v], v] for v in range(naseq)] + bstubs = [[bseq[v - naseq], v] for v in range(naseq, naseq + nbseq)] + while astubs: + astubs.sort() + (degree, u) = astubs.pop() # take of largest degree node in the a set + if degree == 0: + break # done, all are zero + bstubs.sort() + small = bstubs[0 : degree // 2] # add these low degree targets + large = bstubs[(-degree + degree // 2) :] # now high degree targets + stubs = [x for z in zip(large, small) for x in z] # combine, sorry + if len(stubs) < len(small) + len(large): # check for zip truncation + stubs.append(large.pop()) + for target in stubs: + v = target[1] + G.add_edge(u, v) + target[0] -= 1 # note this updates bstubs too. + if target[0] == 0: + bstubs.remove(target) + + G.name = "bipartite_alternating_havel_hakimi_graph" + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def preferential_attachment_graph(aseq, p, create_using=None, seed=None): + """Create a bipartite graph with a preferential attachment model from + a given single degree sequence. + + The graph is composed of two partitions. Set A has nodes 0 to + (len(aseq) - 1) and set B has nodes starting with node len(aseq). + The number of nodes in set B is random. + + Parameters + ---------- + aseq : list + Degree sequence for node set A. + p : float + Probability that a new bottom node is added. + create_using : NetworkX graph instance, optional + Return graph of this type. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + References + ---------- + .. [1] Guillaume, J.L. and Latapy, M., + Bipartite graphs as models of complex networks. + Physica A: Statistical Mechanics and its Applications, + 2006, 371(2), pp.795-813. + .. [2] Jean-Loup Guillaume and Matthieu Latapy, + Bipartite structure of all complex networks, + Inf. Process. Lett. 90, 2004, pg. 215-221 + https://doi.org/10.1016/j.ipl.2004.03.007 + + Notes + ----- + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.preferential_attachment_graph + """ + G = nx.empty_graph(0, create_using, default=nx.MultiGraph) + if G.is_directed(): + raise nx.NetworkXError("Directed Graph not supported") + + if p > 1: + raise nx.NetworkXError(f"probability {p} > 1") + + naseq = len(aseq) + G = _add_nodes_with_bipartite_label(G, naseq, 0) + vv = [[v] * aseq[v] for v in range(naseq)] + while vv: + while vv[0]: + source = vv[0][0] + vv[0].remove(source) + if seed.random() < p or len(G) == naseq: + target = len(G) + G.add_node(target, bipartite=1) + G.add_edge(source, target) + else: + bb = [[b] * G.degree(b) for b in range(naseq, len(G))] + # flatten the list of lists into a list. + bbstubs = reduce(lambda x, y: x + y, bb) + # choose preferentially a bottom node. + target = seed.choice(bbstubs) + G.add_node(target, bipartite=1) + G.add_edge(source, target) + vv.remove(vv[0]) + G.name = "bipartite_preferential_attachment_model" + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def random_graph(n, m, p, seed=None, directed=False): + """Returns a bipartite random graph. + + This is a bipartite version of the binomial (Erdős-Rényi) graph. + The graph is composed of two partitions. Set A has nodes 0 to + (n - 1) and set B has nodes n to (n + m - 1). + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set. + m : int + The number of nodes in the second bipartite set. + p : float + Probability for edge creation. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + directed : bool, optional (default=False) + If True return a directed graph + + Notes + ----- + The bipartite random graph algorithm chooses each of the n*m (undirected) + or 2*nm (directed) possible edges with probability p. + + This algorithm is $O(n+m)$ where $m$ is the expected number of edges. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.random_graph + + See Also + -------- + gnp_random_graph, configuration_model + + References + ---------- + .. [1] Vladimir Batagelj and Ulrik Brandes, + "Efficient generation of large random networks", + Phys. Rev. E, 71, 036113, 2005. + """ + G = nx.Graph() + G = _add_nodes_with_bipartite_label(G, n, m) + if directed: + G = nx.DiGraph(G) + G.name = f"fast_gnp_random_graph({n},{m},{p})" + + if p <= 0: + return G + if p >= 1: + return nx.complete_bipartite_graph(n, m) + + lp = math.log(1.0 - p) + + v = 0 + w = -1 + while v < n: + lr = math.log(1.0 - seed.random()) + w = w + 1 + int(lr / lp) + while w >= m and v < n: + w = w - m + v = v + 1 + if v < n: + G.add_edge(v, n + w) + + if directed: + # use the same algorithm to + # add edges from the "m" to "n" set + v = 0 + w = -1 + while v < n: + lr = math.log(1.0 - seed.random()) + w = w + 1 + int(lr / lp) + while w >= m and v < n: + w = w - m + v = v + 1 + if v < n: + G.add_edge(n + w, v) + + return G + + +@py_random_state(3) +@nx._dispatchable(graphs=None, returns_graph=True) +def gnmk_random_graph(n, m, k, seed=None, directed=False): + """Returns a random bipartite graph G_{n,m,k}. + + Produces a bipartite graph chosen randomly out of the set of all graphs + with n top nodes, m bottom nodes, and k edges. + The graph is composed of two sets of nodes. + Set A has nodes 0 to (n - 1) and set B has nodes n to (n + m - 1). + + Parameters + ---------- + n : int + The number of nodes in the first bipartite set. + m : int + The number of nodes in the second bipartite set. + k : int + The number of edges + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + directed : bool, optional (default=False) + If True return a directed graph + + Examples + -------- + >>> G = nx.bipartite.gnmk_random_graph(10, 20, 50) + + See Also + -------- + gnm_random_graph + + Notes + ----- + If k > m * n then a complete bipartite graph is returned. + + This graph is a bipartite version of the `G_{nm}` random graph model. + + The nodes are assigned the attribute 'bipartite' with the value 0 or 1 + to indicate which bipartite set the node belongs to. + + This function is not imported in the main namespace. + To use it use nx.bipartite.gnmk_random_graph + """ + G = nx.Graph() + G = _add_nodes_with_bipartite_label(G, n, m) + if directed: + G = nx.DiGraph(G) + G.name = f"bipartite_gnm_random_graph({n},{m},{k})" + if n == 1 or m == 1: + return G + max_edges = n * m # max_edges for bipartite networks + if k >= max_edges: # Maybe we should raise an exception here + return nx.complete_bipartite_graph(n, m, create_using=G) + + top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0] + bottom = list(set(G) - set(top)) + edge_count = 0 + while edge_count < k: + # generate random edge,u,v + u = seed.choice(top) + v = seed.choice(bottom) + if v in G[u]: + continue + else: + G.add_edge(u, v) + edge_count += 1 + return G + + +def _add_nodes_with_bipartite_label(G, lena, lenb): + G.add_nodes_from(range(lena + lenb)) + b = dict(zip(range(lena), [0] * lena)) + b.update(dict(zip(range(lena, lena + lenb), [1] * lenb))) + nx.set_node_attributes(G, b, "bipartite") + return G diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/link_analysis.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/link_analysis.py new file mode 100644 index 0000000000000000000000000000000000000000..7238b1ed8523d7ae4e3211e74850cce582cbbe15 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/link_analysis.py @@ -0,0 +1,316 @@ +import itertools + +import networkx as nx + +__all__ = ["birank"] + + +@nx._dispatchable(edge_attrs="weight") +def birank( + G, + nodes, + *, + alpha=None, + beta=None, + top_personalization=None, + bottom_personalization=None, + max_iter=100, + tol=1.0e-6, + weight="weight", +): + r"""Compute the BiRank score for nodes in a bipartite network. + + Given the bipartite sets $U$ and $P$, the BiRank algorithm seeks to satisfy + the following recursive relationships between the scores of nodes $j \in P$ + and $i \in U$: + + .. math:: + + p_j = \alpha \sum_{i \in U} \frac{w_{ij}}{\sqrt{d_i}\sqrt{d_j}} u_i + + (1 - \alpha) p_j^0 + + u_i = \beta \sum_{j \in P} \frac{w_{ij}}{\sqrt{d_i}\sqrt{d_j}} p_j + + (1 - \beta) u_i^0 + + where + + * $p_j$ and $u_i$ are the BiRank scores of nodes $j \in P$ and $i \in U$. + * $w_{ij}$ is the weight of the edge between nodes $i \in U$ and $j \in P$ + (With a value of 0 if no edge exists). + * $d_i$ and $d_j$ are the weighted degrees of nodes $i \in U$ and $j \in P$, + respectively. + * $p_j^0$ and $u_i^0$ are personalization values that can encode a priori + weights for the nodes $j \in P$ and $i \in U$, respectively. Akin to the + personalization vector used by PageRank. + * $\alpha$ and $\beta$ are damping hyperparameters applying to nodes in $P$ + and $U$ respectively. They can take values in the interval $[0, 1]$, and + are analogous to those used by PageRank. + + Below are two use cases for this algorithm. + + 1. Personalized Recommendation System + Given a bipartite graph representing users and items, BiRank can be used + as a collaborative filtering algorithm to recommend items to users. + Previous ratings are encoded as edge weights, and the specific ratings + of an individual user on a set of items is used as the personalization + vector over items. See the example below for an implementation of this + on a toy dataset provided in [1]_. + + 2. Popularity Prediction + Given a bipartite graph representing user interactions with items, e.g. + commits to a GitHub repository, BiRank can be used to predict the + popularity of a given item. Edge weights should encode the strength of + the interaction signal. This could be a raw count, or weighted by a time + decay function like that specified in Eq. (15) of [1]_. The + personalization vectors can be used to encode existing popularity + signals, for example, the monthly download count of a repository's + package. + + Parameters + ---------- + G : graph + A bipartite network + + nodes : iterable of nodes + Container with all nodes belonging to the first bipartite node set + ('top'). The nodes in this set use the hyperparameter `alpha`, and the + personalization dictionary `top_personalization`. The nodes in the second + bipartite node set ('bottom') are automatically determined by taking the + complement of 'top' with respect to the graph `G`. + + alpha : float, optional (default=0.80 if top_personalization not empty, else 1) + Damping factor for the 'top' nodes. Must be in the interval $[0, 1]$. + Larger alpha and beta generally reduce the effect of the personalizations + and increase the number of iterations before convergence. Choice of value + is largely dependent on use case, and experimentation is recommended. + + beta : float, optional (default=0.80 if bottom_personalization not empty, else 1) + Damping factor for the 'bottom' nodes. Must be in the interval $[0, 1]$. + Larger alpha and beta generally reduce the effect of the personalizations + and increase the number of iterations before convergence. Choice of value + is largely dependent on use case, and experimentation is recommended. + + top_personalization : dict, optional (default=None) + Dictionary keyed by nodes in 'top' to that node's personalization value. + Unspecified nodes in 'top' will be assigned a personalization value of 0. + Personalization values are used to encode a priori weights for a given node, + and should be non-negative. + + bottom_personalization : dict, optional (default=None) + Dictionary keyed by nodes in 'bottom' to that node's personalization value. + Unspecified nodes in 'bottom' will be assigned a personalization value of 0. + Personalization values are used to encode a priori weights for a given node, + and should be non-negative. + + max_iter : int, optional (default=100) + Maximum number of iterations in power method eigenvalue solver. + + tol : float, optional (default=1.0e-6) + Error tolerance used to check convergence in power method solver. The + iteration will stop after a tolerance of both ``len(top) * tol`` and + ``len(bottom) * tol`` is reached for nodes in 'top' and 'bottom' + respectively. + + weight : string or None, optional (default='weight') + Edge data key to use as weight. + + Returns + ------- + birank : dictionary + Dictionary keyed by node to that node's BiRank score. + + Raises + ------ + NetworkXAlgorithmError + If the parameters `alpha` or `beta` are not in the interval [0, 1], + if either of the bipartite sets are empty, or if negative values are + provided in the personalization dictionaries. + + PowerIterationFailedConvergence + If the algorithm fails to converge to the specified tolerance + within the specified number of iterations of the power iteration + method. + + Examples + -------- + Construct a bipartite graph with user-item ratings and use BiRank to + recommend items to a user (user 1). The example below uses the `rating` + edge attribute as the weight of the edges. The `top_personalization` vector + is used to encode the user's previous ratings on items. + + Creation of graph, bipartite sets for the example. + + >>> elist = [ + ... ("u1", "p1", 5), + ... ("u2", "p1", 5), + ... ("u2", "p2", 4), + ... ("u3", "p1", 3), + ... ("u3", "p3", 2), + ... ] + >>> G = nx.Graph() + >>> G.add_weighted_edges_from(elist, weight="rating") + >>> product_nodes = ("p1", "p2", "p3") + >>> user = "u1" + + First, we create a personalization vector for the user based on on their + ratings of past items. In this case they have only rated one item (p1, with + a rating of 5) in the past. + + >>> user_personalization = { + ... product: rating + ... for _, product, rating in G.edges(nbunch=user, data="rating") + ... } + >>> user_personalization + {'p1': 5} + + Calculate the BiRank score of all nodes in the graph, filter for the items + that the user has not rated yet, and sort the results by score. + + >>> user_birank_results = nx.bipartite.birank( + ... G, product_nodes, top_personalization=user_personalization, weight="rating" + ... ) + >>> user_birank_results = filter( + ... lambda item: item[0][0] == "p" and user not in G.neighbors(item[0]), + ... user_birank_results.items(), + ... ) + >>> user_birank_results = sorted( + ... user_birank_results, key=lambda item: item[1], reverse=True + ... ) + >>> user_recommendations = { + ... product: round(score, 5) for product, score in user_birank_results + ... } + >>> user_recommendations + {'p2': 1.44818, 'p3': 1.04811} + + We find that user 1 should be recommended item p2 over item p3. This is due + to the fact that user 2 rated also rated p1 highly, while user 3 did not. + Thus user 2's tastes are inferred to be similar to user 1's, and carry more + weight in the recommendation. + + See Also + -------- + :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank` + :func:`~networkx.algorithms.link_analysis.hits_alg.hits` + :func:`~networkx.algorithms.bipartite.centrality.betweenness_centrality` + :func:`~networkx.algorithms.bipartite.basic.sets` + :func:`~networkx.algorithms.bipartite.basic.is_bipartite` + + Notes + ----- + The `nodes` input parameter must contain all nodes in one bipartite + node set, but the dictionary returned contains all nodes from both + bipartite node sets. See :mod:`bipartite documentation + ` for further details on how + bipartite graphs are handled in NetworkX. + + In the case a personalization dictionary is not provided for top (bottom) + `alpha` (`beta`) will default to 1. This is because a damping factor + without a non-zero entry in the personalization vector will lead to the + algorithm converging to the zero vector. + + References + ---------- + .. [1] Xiangnan He, Ming Gao, Min-Yen Kan, and Dingxian Wang. 2017. + BiRank: Towards Ranking on Bipartite Graphs. IEEE Trans. on Knowl. + and Data Eng. 29, 1 (January 2017), 57–71. + https://arxiv.org/pdf/1708.04396 + + """ + import numpy as np + import scipy as sp + + # Initialize the sets of top and bottom nodes + top = set(nodes) + bottom = set(G) - top + top_count = len(top) + bottom_count = len(bottom) + + if top_count == 0 or bottom_count == 0: + raise nx.NetworkXAlgorithmError( + "The BiRank algorithm requires a bipartite graph with at least one" + "node in each set." + ) + + # Clean the personalization dictionaries + top_personalization = _clean_personalization_dict(top_personalization) + bottom_personalization = _clean_personalization_dict(bottom_personalization) + + # Set default values for alpha and beta if not provided + if alpha is None: + alpha = 0.8 if top_personalization else 1 + if beta is None: + beta = 0.8 if bottom_personalization else 1 + + if alpha < 0 or alpha > 1: + raise nx.NetworkXAlgorithmError("alpha must be in the interval [0, 1]") + if beta < 0 or beta > 1: + raise nx.NetworkXAlgorithmError("beta must be in the interval [0, 1]") + + # Initialize query vectors + p0 = np.array([top_personalization.get(n, 0) for n in top], dtype=float) + u0 = np.array([bottom_personalization.get(n, 0) for n in bottom], dtype=float) + + # Construct degree normalized biadjacency matrix `S` and its transpose + W = nx.bipartite.biadjacency_matrix(G, bottom, top, weight=weight, dtype=float) + p_degrees = W.sum(axis=0, dtype=float) + # Handle case where the node is disconnected - avoids warning + p_degrees[p_degrees == 0] = 1.0 + D_p = sp.sparse.dia_array( + ([1.0 / np.sqrt(p_degrees)], [0]), + shape=(top_count, top_count), + dtype=float, + ) + u_degrees = W.sum(axis=1, dtype=float) + u_degrees[u_degrees == 0] = 1.0 + D_u = sp.sparse.dia_array( + ([1.0 / np.sqrt(u_degrees)], [0]), + shape=(bottom_count, bottom_count), + dtype=float, + ) + S = D_u.tocsr() @ W @ D_p.tocsr() + S_T = S.T + + # Initialize birank vectors for iteration + p = np.ones(top_count, dtype=float) / top_count + u = beta * (S @ p) + (1 - beta) * u0 + + # Iterate until convergence + for _ in range(max_iter): + p_last = p + u_last = u + p = alpha * (S_T @ u) + (1 - alpha) * p0 + u = beta * (S @ p) + (1 - beta) * u0 + + # Continue iterating if the error (absolute if less than 1, relative otherwise) + # is above the tolerance threshold for either p or u + err_u = np.absolute((u_last - u) / np.maximum(1.0, u_last)).sum() + if err_u >= len(u) * tol: + continue + err_p = np.absolute((p_last - p) / np.maximum(1.0, p_last)).sum() + if err_p >= len(p) * tol: + continue + + # Handle edge case where if both alpha and beta are 1, scale is + # indeterminate, so normalization is required to return consistent results + if alpha == 1 and beta == 1: + p = p / np.linalg.norm(p, 1) + u = u / np.linalg.norm(u, 1) + + # If both error thresholds pass, return a single dictionary mapping + # nodes to their scores + return dict( + zip(itertools.chain(top, bottom), map(float, itertools.chain(p, u))) + ) + + # If we reach this point, we have not converged + raise nx.PowerIterationFailedConvergence(max_iter) + + +def _clean_personalization_dict(personalization): + """Filter out zero values from the personalization dictionary, + handle case where None is passed, ensure values are non-negative.""" + if personalization is None: + return {} + if any(value < 0 for value in personalization.values()): + raise nx.NetworkXAlgorithmError("Personalization values must be non-negative.") + return {node: value for node, value in personalization.items() if value != 0} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matching.py new file mode 100644 index 0000000000000000000000000000000000000000..38a174780ac1eb7a42568aa6752b9adb82a2d984 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matching.py @@ -0,0 +1,590 @@ +# This module uses material from the Wikipedia article Hopcroft--Karp algorithm +# , accessed on +# January 3, 2015, which is released under the Creative Commons +# Attribution-Share-Alike License 3.0 +# . That article includes +# pseudocode, which has been translated into the corresponding Python code. +# +# Portions of this module use code from David Eppstein's Python Algorithms and +# Data Structures (PADS) library, which is dedicated to the public domain (for +# proof, see ). +"""Provides functions for computing maximum cardinality matchings and minimum +weight full matchings in a bipartite graph. + +If you don't care about the particular implementation of the maximum matching +algorithm, simply use the :func:`maximum_matching`. If you do care, you can +import one of the named maximum matching algorithms directly. + +For example, to find a maximum matching in the complete bipartite graph with +two vertices on the left and three vertices on the right: + +>>> G = nx.complete_bipartite_graph(2, 3) +>>> left, right = nx.bipartite.sets(G) +>>> list(left) +[0, 1] +>>> list(right) +[2, 3, 4] +>>> nx.bipartite.maximum_matching(G) +{0: 2, 1: 3, 2: 0, 3: 1} + +The dictionary returned by :func:`maximum_matching` includes a mapping for +vertices in both the left and right vertex sets. + +Similarly, :func:`minimum_weight_full_matching` produces, for a complete +weighted bipartite graph, a matching whose cardinality is the cardinality of +the smaller of the two partitions, and for which the sum of the weights of the +edges included in the matching is minimal. + +""" + +import collections +import itertools + +import networkx as nx +from networkx.algorithms.bipartite import sets as bipartite_sets +from networkx.algorithms.bipartite.matrix import biadjacency_matrix + +__all__ = [ + "maximum_matching", + "hopcroft_karp_matching", + "eppstein_matching", + "to_vertex_cover", + "minimum_weight_full_matching", +] + +INFINITY = float("inf") + + +@nx._dispatchable +def hopcroft_karp_matching(G, top_nodes=None): + """Returns the maximum cardinality matching of the bipartite graph `G`. + + A matching is a set of edges that do not share any nodes. A maximum + cardinality matching is a matching with the most edges possible. It + is not always unique. Finding a matching in a bipartite graph can be + treated as a networkx flow problem. + + The functions ``hopcroft_karp_matching`` and ``maximum_matching`` + are aliases of the same function. + + Parameters + ---------- + G : NetworkX graph + + Undirected bipartite graph + + top_nodes : container of nodes + + Container with all nodes in one bipartite node set. If not supplied + it will be computed. But if more than one solution exists an exception + will be raised. + + Returns + ------- + matches : dictionary + + The matching is returned as a dictionary, `matches`, such that + ``matches[v] == w`` if node `v` is matched to node `w`. Unmatched + nodes do not occur as a key in `matches`. + + Raises + ------ + AmbiguousSolution + Raised if the input bipartite graph is disconnected and no container + with all nodes in one bipartite set is provided. When determining + the nodes in each bipartite set more than one valid solution is + possible if the input graph is disconnected. + + Notes + ----- + This function is implemented with the `Hopcroft--Karp matching algorithm + `_ for + bipartite graphs. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + maximum_matching + hopcroft_karp_matching + eppstein_matching + + References + ---------- + .. [1] John E. Hopcroft and Richard M. Karp. "An n^{5 / 2} Algorithm for + Maximum Matchings in Bipartite Graphs" In: **SIAM Journal of Computing** + 2.4 (1973), pp. 225--231. . + + """ + + # First we define some auxiliary search functions. + # + # If you are a human reading these auxiliary search functions, the "global" + # variables `leftmatches`, `rightmatches`, `distances`, etc. are defined + # below the functions, so that they are initialized close to the initial + # invocation of the search functions. + def breadth_first_search(): + for v in left: + if leftmatches[v] is None: + distances[v] = 0 + queue.append(v) + else: + distances[v] = INFINITY + distances[None] = INFINITY + while queue: + v = queue.popleft() + if distances[v] < distances[None]: + for u in G[v]: + if distances[rightmatches[u]] is INFINITY: + distances[rightmatches[u]] = distances[v] + 1 + queue.append(rightmatches[u]) + return distances[None] is not INFINITY + + def depth_first_search(v): + if v is not None: + for u in G[v]: + if distances[rightmatches[u]] == distances[v] + 1: + if depth_first_search(rightmatches[u]): + rightmatches[u] = v + leftmatches[v] = u + return True + distances[v] = INFINITY + return False + return True + + # Initialize the "global" variables that maintain state during the search. + left, right = bipartite_sets(G, top_nodes) + leftmatches = {v: None for v in left} + rightmatches = {v: None for v in right} + distances = {} + queue = collections.deque() + + # Implementation note: this counter is incremented as pairs are matched but + # it is currently not used elsewhere in the computation. + num_matched_pairs = 0 + while breadth_first_search(): + for v in left: + if leftmatches[v] is None: + if depth_first_search(v): + num_matched_pairs += 1 + + # Strip the entries matched to `None`. + leftmatches = {k: v for k, v in leftmatches.items() if v is not None} + rightmatches = {k: v for k, v in rightmatches.items() if v is not None} + + # At this point, the left matches and the right matches are inverses of one + # another. In other words, + # + # leftmatches == {v, k for k, v in rightmatches.items()} + # + # Finally, we combine both the left matches and right matches. + return dict(itertools.chain(leftmatches.items(), rightmatches.items())) + + +@nx._dispatchable +def eppstein_matching(G, top_nodes=None): + """Returns the maximum cardinality matching of the bipartite graph `G`. + + Parameters + ---------- + G : NetworkX graph + + Undirected bipartite graph + + top_nodes : container + + Container with all nodes in one bipartite node set. If not supplied + it will be computed. But if more than one solution exists an exception + will be raised. + + Returns + ------- + matches : dictionary + + The matching is returned as a dictionary, `matching`, such that + ``matching[v] == w`` if node `v` is matched to node `w`. Unmatched + nodes do not occur as a key in `matching`. + + Raises + ------ + AmbiguousSolution + Raised if the input bipartite graph is disconnected and no container + with all nodes in one bipartite set is provided. When determining + the nodes in each bipartite set more than one valid solution is + possible if the input graph is disconnected. + + Notes + ----- + This function is implemented with David Eppstein's version of the algorithm + Hopcroft--Karp algorithm (see :func:`hopcroft_karp_matching`), which + originally appeared in the `Python Algorithms and Data Structures library + (PADS) `_. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + + hopcroft_karp_matching + + """ + # Due to its original implementation, a directed graph is needed + # so that the two sets of bipartite nodes can be distinguished + left, right = bipartite_sets(G, top_nodes) + G = nx.DiGraph(G.edges(left)) + # initialize greedy matching (redundant, but faster than full search) + matching = {} + for u in G: + for v in G[u]: + if v not in matching: + matching[v] = u + break + while True: + # structure residual graph into layers + # pred[u] gives the neighbor in the previous layer for u in U + # preds[v] gives a list of neighbors in the previous layer for v in V + # unmatched gives a list of unmatched vertices in final layer of V, + # and is also used as a flag value for pred[u] when u is in the first + # layer + preds = {} + unmatched = [] + pred = {u: unmatched for u in G} + for v in matching: + del pred[matching[v]] + layer = list(pred) + + # repeatedly extend layering structure by another pair of layers + while layer and not unmatched: + newLayer = {} + for u in layer: + for v in G[u]: + if v not in preds: + newLayer.setdefault(v, []).append(u) + layer = [] + for v in newLayer: + preds[v] = newLayer[v] + if v in matching: + layer.append(matching[v]) + pred[matching[v]] = v + else: + unmatched.append(v) + + # did we finish layering without finding any alternating paths? + if not unmatched: + # TODO - The lines between --- were unused and were thus commented + # out. This whole commented chunk should be reviewed to determine + # whether it should be built upon or completely removed. + # --- + # unlayered = {} + # for u in G: + # # TODO Why is extra inner loop necessary? + # for v in G[u]: + # if v not in preds: + # unlayered[v] = None + # --- + # TODO Originally, this function returned a three-tuple: + # + # return (matching, list(pred), list(unlayered)) + # + # For some reason, the documentation for this function + # indicated that the second and third elements of the returned + # three-tuple would be the vertices in the left and right vertex + # sets, respectively, that are also in the maximum independent set. + # However, what I think the author meant was that the second + # element is the list of vertices that were unmatched and the third + # element was the list of vertices that were matched. Since that + # seems to be the case, they don't really need to be returned, + # since that information can be inferred from the matching + # dictionary. + + # All the matched nodes must be a key in the dictionary + for key in matching.copy(): + matching[matching[key]] = key + return matching + + # recursively search backward through layers to find alternating paths + # recursion returns true if found path, false otherwise + def recurse(v): + if v in preds: + L = preds.pop(v) + for u in L: + if u in pred: + pu = pred.pop(u) + if pu is unmatched or recurse(pu): + matching[v] = u + return True + return False + + for v in unmatched: + recurse(v) + + +def _is_connected_by_alternating_path(G, v, matched_edges, unmatched_edges, targets): + """Returns True if and only if the vertex `v` is connected to one of + the target vertices by an alternating path in `G`. + + An *alternating path* is a path in which every other edge is in the + specified maximum matching (and the remaining edges in the path are not in + the matching). An alternating path may have matched edges in the even + positions or in the odd positions, as long as the edges alternate between + 'matched' and 'unmatched'. + + `G` is an undirected bipartite NetworkX graph. + + `v` is a vertex in `G`. + + `matched_edges` is a set of edges present in a maximum matching in `G`. + + `unmatched_edges` is a set of edges not present in a maximum + matching in `G`. + + `targets` is a set of vertices. + + """ + + def _alternating_dfs(u, along_matched=True): + """Returns True if and only if `u` is connected to one of the + targets by an alternating path. + + `u` is a vertex in the graph `G`. + + If `along_matched` is True, this step of the depth-first search + will continue only through edges in the given matching. Otherwise, it + will continue only through edges *not* in the given matching. + + """ + visited = set() + # Follow matched edges when depth is even, + # and follow unmatched edges when depth is odd. + initial_depth = 0 if along_matched else 1 + stack = [(u, iter(G[u]), initial_depth)] + while stack: + parent, children, depth = stack[-1] + valid_edges = matched_edges if depth % 2 else unmatched_edges + try: + child = next(children) + if child not in visited: + if (parent, child) in valid_edges or (child, parent) in valid_edges: + if child in targets: + return True + visited.add(child) + stack.append((child, iter(G[child]), depth + 1)) + except StopIteration: + stack.pop() + return False + + # Check for alternating paths starting with edges in the matching, then + # check for alternating paths starting with edges not in the + # matching. + return _alternating_dfs(v, along_matched=True) or _alternating_dfs( + v, along_matched=False + ) + + +def _connected_by_alternating_paths(G, matching, targets): + """Returns the set of vertices that are connected to one of the target + vertices by an alternating path in `G` or are themselves a target. + + An *alternating path* is a path in which every other edge is in the + specified maximum matching (and the remaining edges in the path are not in + the matching). An alternating path may have matched edges in the even + positions or in the odd positions, as long as the edges alternate between + 'matched' and 'unmatched'. + + `G` is an undirected bipartite NetworkX graph. + + `matching` is a dictionary representing a maximum matching in `G`, as + returned by, for example, :func:`maximum_matching`. + + `targets` is a set of vertices. + + """ + # Get the set of matched edges and the set of unmatched edges. Only include + # one version of each undirected edge (for example, include edge (1, 2) but + # not edge (2, 1)). Using frozensets as an intermediary step we do not + # require nodes to be orderable. + edge_sets = {frozenset((u, v)) for u, v in matching.items()} + matched_edges = {tuple(edge) for edge in edge_sets} + unmatched_edges = { + (u, v) for (u, v) in G.edges() if frozenset((u, v)) not in edge_sets + } + + return { + v + for v in G + if v in targets + or _is_connected_by_alternating_path( + G, v, matched_edges, unmatched_edges, targets + ) + } + + +@nx._dispatchable +def to_vertex_cover(G, matching, top_nodes=None): + """Returns the minimum vertex cover corresponding to the given maximum + matching of the bipartite graph `G`. + + Parameters + ---------- + G : NetworkX graph + + Undirected bipartite graph + + matching : dictionary + + A dictionary whose keys are vertices in `G` and whose values are the + distinct neighbors comprising the maximum matching for `G`, as returned + by, for example, :func:`maximum_matching`. The dictionary *must* + represent the maximum matching. + + top_nodes : container + + Container with all nodes in one bipartite node set. If not supplied + it will be computed. But if more than one solution exists an exception + will be raised. + + Returns + ------- + vertex_cover : :class:`set` + + The minimum vertex cover in `G`. + + Raises + ------ + AmbiguousSolution + Raised if the input bipartite graph is disconnected and no container + with all nodes in one bipartite set is provided. When determining + the nodes in each bipartite set more than one valid solution is + possible if the input graph is disconnected. + + Notes + ----- + This function is implemented using the procedure guaranteed by `Konig's + theorem + `_, + which proves an equivalence between a maximum matching and a minimum vertex + cover in bipartite graphs. + + Since a minimum vertex cover is the complement of a maximum independent set + for any graph, one can compute the maximum independent set of a bipartite + graph this way: + + >>> G = nx.complete_bipartite_graph(2, 3) + >>> matching = nx.bipartite.maximum_matching(G) + >>> vertex_cover = nx.bipartite.to_vertex_cover(G, matching) + >>> independent_set = set(G) - vertex_cover + >>> print(list(independent_set)) + [2, 3, 4] + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + """ + # This is a Python implementation of the algorithm described at + # . + L, R = bipartite_sets(G, top_nodes) + # Let U be the set of unmatched vertices in the left vertex set. + unmatched_vertices = set(G) - set(matching) + U = unmatched_vertices & L + # Let Z be the set of vertices that are either in U or are connected to U + # by alternating paths. + Z = _connected_by_alternating_paths(G, matching, U) + # At this point, every edge either has a right endpoint in Z or a left + # endpoint not in Z. This gives us the vertex cover. + return (L - Z) | (R & Z) + + +#: Returns the maximum cardinality matching in the given bipartite graph. +#: +#: This function is simply an alias for :func:`hopcroft_karp_matching`. +maximum_matching = hopcroft_karp_matching + + +@nx._dispatchable(edge_attrs="weight") +def minimum_weight_full_matching(G, top_nodes=None, weight="weight"): + r"""Returns a minimum weight full matching of the bipartite graph `G`. + + Let :math:`G = ((U, V), E)` be a weighted bipartite graph with real weights + :math:`w : E \to \mathbb{R}`. This function then produces a matching + :math:`M \subseteq E` with cardinality + + .. math:: + \lvert M \rvert = \min(\lvert U \rvert, \lvert V \rvert), + + which minimizes the sum of the weights of the edges included in the + matching, :math:`\sum_{e \in M} w(e)`, or raises an error if no such + matching exists. + + When :math:`\lvert U \rvert = \lvert V \rvert`, this is commonly + referred to as a perfect matching; here, since we allow + :math:`\lvert U \rvert` and :math:`\lvert V \rvert` to differ, we + follow Karp [1]_ and refer to the matching as *full*. + + Parameters + ---------- + G : NetworkX graph + + Undirected bipartite graph + + top_nodes : container + + Container with all nodes in one bipartite node set. If not supplied + it will be computed. + + weight : string, optional (default='weight') + + The edge data key used to provide each value in the matrix. + If None, then each edge has weight 1. + + Returns + ------- + matches : dictionary + + The matching is returned as a dictionary, `matches`, such that + ``matches[v] == w`` if node `v` is matched to node `w`. Unmatched + nodes do not occur as a key in `matches`. + + Raises + ------ + ValueError + Raised if no full matching exists. + + ImportError + Raised if SciPy is not available. + + Notes + ----- + The problem of determining a minimum weight full matching is also known as + the rectangular linear assignment problem. This implementation defers the + calculation of the assignment to SciPy. + + References + ---------- + .. [1] Richard Manning Karp: + An algorithm to Solve the m x n Assignment Problem in Expected Time + O(mn log n). + Networks, 10(2):143–152, 1980. + + """ + import numpy as np + import scipy as sp + + left, right = nx.bipartite.sets(G, top_nodes) + U = list(left) + V = list(right) + # We explicitly create the biadjacency matrix having infinities + # where edges are missing (as opposed to zeros, which is what one would + # get by using toarray on the sparse matrix). + weights_sparse = biadjacency_matrix( + G, row_order=U, column_order=V, weight=weight, format="coo" + ) + weights = np.full(weights_sparse.shape, np.inf) + weights[weights_sparse.row, weights_sparse.col] = weights_sparse.data + left_matches = sp.optimize.linear_sum_assignment(weights) + d = {U[u]: V[v] for u, v in zip(*left_matches)} + # d will contain the matching from edges in left to right; we need to + # add the ones from right to left as well. + d.update({v: u for u, v in d.items()}) + return d diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matrix.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..5062dcdea91cfa3d7b5b67ec5c69a4815d14b650 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/matrix.py @@ -0,0 +1,232 @@ +""" +==================== +Biadjacency matrices +==================== +""" + +import itertools + +import networkx as nx +from networkx.convert_matrix import _generate_weighted_edges + +__all__ = ["biadjacency_matrix", "from_biadjacency_matrix"] + + +@nx._dispatchable(edge_attrs="weight") +def biadjacency_matrix( + G, row_order, column_order=None, dtype=None, weight="weight", format="csr" +): + r"""Returns the biadjacency matrix of the bipartite graph G. + + Let `G = (U, V, E)` be a bipartite graph with node sets + `U = u_{1},...,u_{r}` and `V = v_{1},...,v_{s}`. The biadjacency + matrix [1]_ is the `r` x `s` matrix `B` in which `b_{i,j} = 1` + if, and only if, `(u_i, v_j) \in E`. If the parameter `weight` is + not `None` and matches the name of an edge attribute, its value is + used instead of 1. + + Parameters + ---------- + G : graph + A NetworkX graph + + row_order : list of nodes + The rows of the matrix are ordered according to the list of nodes. + + column_order : list, optional + The columns of the matrix are ordered according to the list of nodes. + If column_order is None, then the ordering of columns is arbitrary. + + dtype : NumPy data-type, optional + A valid NumPy dtype used to initialize the array. If None, then the + NumPy default is used. + + weight : string or None, optional (default='weight') + The edge data key used to provide each value in the matrix. + If None, then each edge has weight 1. + + format : str in {'dense', 'bsr', 'csr', 'csc', 'coo', 'lil', 'dia', 'dok'} + The type of the matrix to be returned (default 'csr'). For + some algorithms different implementations of sparse matrices + can perform better. See [2]_ for details. + + Returns + ------- + M : SciPy sparse array + Biadjacency matrix representation of the bipartite graph G. + + Notes + ----- + No attempt is made to check that the input graph is bipartite. + + For directed bipartite graphs only successors are considered as neighbors. + To obtain an adjacency matrix with ones (or weight values) for both + predecessors and successors you have to generate two biadjacency matrices + where the rows of one of them are the columns of the other, and then add + one to the transpose of the other. + + See Also + -------- + adjacency_matrix + from_biadjacency_matrix + + References + ---------- + .. [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph + .. [2] Scipy Dev. References, "Sparse Matrices", + https://docs.scipy.org/doc/scipy/reference/sparse.html + """ + import scipy as sp + + nlen = len(row_order) + if nlen == 0: + raise nx.NetworkXError("row_order is empty list") + if len(row_order) != len(set(row_order)): + msg = "Ambiguous ordering: `row_order` contained duplicates." + raise nx.NetworkXError(msg) + if column_order is None: + column_order = list(set(G) - set(row_order)) + mlen = len(column_order) + if len(column_order) != len(set(column_order)): + msg = "Ambiguous ordering: `column_order` contained duplicates." + raise nx.NetworkXError(msg) + + row_index = dict(zip(row_order, itertools.count())) + col_index = dict(zip(column_order, itertools.count())) + + if G.number_of_edges() == 0: + row, col, data = [], [], [] + else: + row, col, data = zip( + *( + (row_index[u], col_index[v], d.get(weight, 1)) + for u, v, d in G.edges(row_order, data=True) + if u in row_index and v in col_index + ) + ) + A = sp.sparse.coo_array((data, (row, col)), shape=(nlen, mlen), dtype=dtype) + try: + return A.asformat(format) + except ValueError as err: + raise nx.NetworkXError(f"Unknown sparse array format: {format}") from err + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_biadjacency_matrix( + A, + create_using=None, + edge_attribute="weight", + *, + row_order=None, + column_order=None, +): + r"""Creates a new bipartite graph from a biadjacency matrix given as a + SciPy sparse array. + + Parameters + ---------- + A : scipy sparse array + A biadjacency matrix representation of a graph + + create_using : NetworkX graph + Use specified graph for result. The default is Graph() + + edge_attribute : string + Name of edge attribute to store matrix numeric value. The data will + have the same type as the matrix entry (int, float, (real,imag)). + + row_order : list, optional (default: range(number of rows in `A`)) + A list of the nodes represented by the rows of the matrix `A`. Will + be represented in the graph as nodes with the `bipartite` attribute set + to 0. Must be the same length as the number of rows in `A`. + + column_order : list, optional (default: range(number of columns in `A`)) + A list of the nodes represented by the columns of the matrix `A`. Will + be represented in the graph as nodes with the `bipartite` attribute set + to 1. Must be the same length as the number of columns in `A`. + + Returns + ------- + G : NetworkX graph + A bipartite graph with edges from the biadjacency matrix `A`, and + nodes from `row_order` and `column_order`. + + Raises + ------ + ValueError + If `row_order` or `column_order` are provided and are not the same + length as the number of rows or columns in `A`, respectively. + + Notes + ----- + The nodes are labeled with the attribute `bipartite` set to an integer + 0 or 1 representing membership in the `top` set (`bipartite=0`) or `bottom` + set (`bipartite=1`) of the bipartite graph. + + If `create_using` is an instance of :class:`networkx.MultiGraph` or + :class:`networkx.MultiDiGraph` and the entries of `A` are of + type :class:`int`, then this function returns a multigraph (of the same + type as `create_using`) with parallel edges. In this case, `edge_attribute` + will be ignored. + + See Also + -------- + biadjacency_matrix + from_numpy_array + + References + ---------- + [1] https://en.wikipedia.org/wiki/Adjacency_matrix#Adjacency_matrix_of_a_bipartite_graph + """ + G = nx.empty_graph(0, create_using) + n, m = A.shape + # Check/set row_order and column_order to have correct length and default values + row_order, column_order = _validate_initialize_bipartite_nodelists( + A, row_order, column_order + ) + + # Make sure we get even the isolated nodes of the graph. + G.add_nodes_from(range(n), bipartite=0) + G.add_nodes_from(range(n, n + m), bipartite=1) + # Create an iterable over (u, v, w) triples and for each triple, add an + # edge from u to v with weight w. + triples = ((u, n + v, d) for (u, v, d) in _generate_weighted_edges(A)) + # If the entries in the adjacency matrix are integers and the graph is a + # multigraph, then create parallel edges, each with weight 1, for each + # entry in the adjacency matrix. Otherwise, create one edge for each + # positive entry in the adjacency matrix and set the weight of that edge to + # be the entry in the matrix. + if A.dtype.kind in ("i", "u") and G.is_multigraph(): + chain = itertools.chain.from_iterable + triples = chain(((u, v, 1) for d in range(w)) for (u, v, w) in triples) + G.add_weighted_edges_from(triples, weight=edge_attribute) + + # If the user provided nodelists, relabel the nodes of the graph inplace + mapping = dict( + itertools.chain(zip(range(n), row_order), zip(range(n, n + m), column_order)) + ) + if len(mapping): + nx.relabel_nodes(G, mapping, copy=False) + return G + + +def _validate_initialize_bipartite_nodelists(A, row_order, column_order): + n, m = A.shape + # Validate nodelists if provided + if row_order is not None: + if len(row_order) != n: + raise ValueError( + "Length of row_order does not match number of rows in A ({n})" + ) + else: + row_order = [] + + if column_order is not None: + if len(column_order) != m: + raise ValueError( + "Length of column_order does not match number of columns in A ({m})" + ) + else: + column_order = [] + + return row_order, column_order diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/projection.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/projection.py new file mode 100644 index 0000000000000000000000000000000000000000..7c2a26cf73ddf39e51fbd20d442abe736acedddd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/projection.py @@ -0,0 +1,526 @@ +"""One-mode (unipartite) projections of bipartite graphs.""" + +import networkx as nx +from networkx.exception import NetworkXAlgorithmError +from networkx.utils import not_implemented_for + +__all__ = [ + "projected_graph", + "weighted_projected_graph", + "collaboration_weighted_projected_graph", + "overlap_weighted_projected_graph", + "generic_weighted_projected_graph", +] + + +@nx._dispatchable( + graphs="B", preserve_node_attrs=True, preserve_graph_attrs=True, returns_graph=True +) +def projected_graph(B, nodes, multigraph=False): + r"""Returns the projection of B onto one of its node sets. + + Returns the graph G that is the projection of the bipartite graph B + onto the specified nodes. They retain their attributes and are connected + in G if they have a common neighbor in B. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + multigraph: bool (default=False) + If True return a multigraph where the multiple edges represent multiple + shared neighbors. They edge key in the multigraph is assigned to the + label of the neighbor. + + Returns + ------- + Graph : NetworkX graph or multigraph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(4) + >>> G = bipartite.projected_graph(B, [1, 3]) + >>> list(G) + [1, 3] + >>> list(G.edges()) + [(1, 3)] + + If nodes `a`, and `b` are connected through both nodes 1 and 2 then + building a multigraph results in two edges in the projection onto + [`a`, `b`]: + + >>> B = nx.Graph() + >>> B.add_edges_from([("a", 1), ("b", 1), ("a", 2), ("b", 2)]) + >>> G = bipartite.projected_graph(B, ["a", "b"], multigraph=True) + >>> print([sorted((u, v)) for u, v in G.edges()]) + [['a', 'b'], ['a', 'b']] + + Notes + ----- + No attempt is made to verify that the input graph B is bipartite. + Returns a simple graph that is the projection of the bipartite graph B + onto the set of nodes given in list nodes. If multigraph=True then + a multigraph is returned with an edge for every shared neighbor. + + Directed graphs are allowed as input. The output will also then + be a directed graph with edges if there is a directed path between + the nodes. + + The graph and node properties are (shallow) copied to the projected graph. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph + """ + if B.is_multigraph(): + raise nx.NetworkXError("not defined for multigraphs") + if B.is_directed(): + directed = True + if multigraph: + G = nx.MultiDiGraph() + else: + G = nx.DiGraph() + else: + directed = False + if multigraph: + G = nx.MultiGraph() + else: + G = nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n, B.nodes[n]) for n in nodes) + for u in nodes: + nbrs2 = {v for nbr in B[u] for v in B[nbr] if v != u} + if multigraph: + for n in nbrs2: + if directed: + links = set(B[u]) & set(B.pred[n]) + else: + links = set(B[u]) & set(B[n]) + for l in links: + if not G.has_edge(u, n, l): + G.add_edge(u, n, key=l) + else: + G.add_edges_from((u, n) for n in nbrs2) + return G + + +@not_implemented_for("multigraph") +@nx._dispatchable(graphs="B", returns_graph=True) +def weighted_projected_graph(B, nodes, ratio=False): + r"""Returns a weighted projection of B onto one of its node sets. + + The weighted projected graph is the projection of the bipartite + network B onto the specified nodes with weights representing the + number of shared neighbors or the ratio between actual shared + neighbors and possible shared neighbors if ``ratio is True`` [1]_. + The nodes retain their attributes and are connected in the resulting + graph if they have an edge to a common node in the original graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Distinct nodes to project onto (the "bottom" nodes). + + ratio: Bool (default=False) + If True, edge weight is the ratio between actual shared neighbors + and maximum possible shared neighbors (i.e., the size of the other + node set). If False, edges weight is the number of shared neighbors. + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(4) + >>> G = bipartite.weighted_projected_graph(B, [1, 3]) + >>> list(G) + [1, 3] + >>> list(G.edges(data=True)) + [(1, 3, {'weight': 1})] + >>> G = bipartite.weighted_projected_graph(B, [1, 3], ratio=True) + >>> list(G.edges(data=True)) + [(1, 3, {'weight': 0.5})] + + Notes + ----- + No attempt is made to verify that the input graph B is bipartite, or that + the input nodes are distinct. However, if the length of the input nodes is + greater than or equal to the nodes in the graph B, an exception is raised. + If the nodes are not distinct but don't raise this error, the output weights + will be incorrect. + The graph and node properties are (shallow) copied to the projected graph. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph + projected_graph + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. "Analyzing Affiliation + Networks". In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + """ + if B.is_directed(): + pred = B.pred + G = nx.DiGraph() + else: + pred = B.adj + G = nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n, B.nodes[n]) for n in nodes) + n_top = len(B) - len(nodes) + + if n_top < 1: + raise NetworkXAlgorithmError( + f"the size of the nodes to project onto ({len(nodes)}) is >= the graph size ({len(B)}).\n" + "They are either not a valid bipartite partition or contain duplicates" + ) + + for u in nodes: + unbrs = set(B[u]) + nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u} + for v in nbrs2: + vnbrs = set(pred[v]) + common = unbrs & vnbrs + if not ratio: + weight = len(common) + else: + weight = len(common) / n_top + G.add_edge(u, v, weight=weight) + return G + + +@not_implemented_for("multigraph") +@nx._dispatchable(graphs="B", returns_graph=True) +def collaboration_weighted_projected_graph(B, nodes): + r"""Newman's weighted projection of B onto one of its node sets. + + The collaboration weighted projection is the projection of the + bipartite network B onto the specified nodes with weights assigned + using Newman's collaboration model [1]_: + + .. math:: + + w_{u, v} = \sum_k \frac{\delta_{u}^{k} \delta_{v}^{k}}{d_k - 1} + + where `u` and `v` are nodes from the bottom bipartite node set, + and `k` is a node of the top node set. + The value `d_k` is the degree of node `k` in the bipartite + network and `\delta_{u}^{k}` is 1 if node `u` is + linked to node `k` in the original bipartite graph or 0 otherwise. + + The nodes retain their attributes and are connected in the resulting + graph if have an edge to a common node in the original bipartite + graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(5) + >>> B.add_edge(1, 5) + >>> G = bipartite.collaboration_weighted_projected_graph(B, [0, 2, 4, 5]) + >>> list(G) + [0, 2, 4, 5] + >>> for edge in sorted(G.edges(data=True)): + ... print(edge) + (0, 2, {'weight': 0.5}) + (0, 5, {'weight': 0.5}) + (2, 4, {'weight': 1.0}) + (2, 5, {'weight': 0.5}) + + Notes + ----- + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + overlap_weighted_projected_graph, + generic_weighted_projected_graph, + projected_graph + + References + ---------- + .. [1] Scientific collaboration networks: II. + Shortest paths, weighted networks, and centrality, + M. E. J. Newman, Phys. Rev. E 64, 016132 (2001). + """ + if B.is_directed(): + pred = B.pred + G = nx.DiGraph() + else: + pred = B.adj + G = nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n, B.nodes[n]) for n in nodes) + for u in nodes: + unbrs = set(B[u]) + nbrs2 = {n for nbr in unbrs for n in B[nbr] if n != u} + for v in nbrs2: + vnbrs = set(pred[v]) + common_degree = (len(B[n]) for n in unbrs & vnbrs) + weight = sum(1.0 / (deg - 1) for deg in common_degree if deg > 1) + G.add_edge(u, v, weight=weight) + return G + + +@not_implemented_for("multigraph") +@nx._dispatchable(graphs="B", returns_graph=True) +def overlap_weighted_projected_graph(B, nodes, jaccard=True): + r"""Overlap weighted projection of B onto one of its node sets. + + The overlap weighted projection is the projection of the bipartite + network B onto the specified nodes with weights representing + the Jaccard index between the neighborhoods of the two nodes in the + original bipartite network [1]_: + + .. math:: + + w_{v, u} = \frac{|N(u) \cap N(v)|}{|N(u) \cup N(v)|} + + or if the parameter 'jaccard' is False, the fraction of common + neighbors by minimum of both nodes degree in the original + bipartite graph [1]_: + + .. math:: + + w_{v, u} = \frac{|N(u) \cap N(v)|}{min(|N(u)|, |N(v)|)} + + The nodes retain their attributes and are connected in the resulting + graph if have an edge to a common node in the original bipartite graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + jaccard: Bool (default=True) + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> B = nx.path_graph(5) + >>> nodes = [0, 2, 4] + >>> G = bipartite.overlap_weighted_projected_graph(B, nodes) + >>> list(G) + [0, 2, 4] + >>> list(G.edges(data=True)) + [(0, 2, {'weight': 0.5}), (2, 4, {'weight': 0.5})] + >>> G = bipartite.overlap_weighted_projected_graph(B, nodes, jaccard=False) + >>> list(G.edges(data=True)) + [(0, 2, {'weight': 1.0}), (2, 4, {'weight': 1.0})] + + Notes + ----- + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + generic_weighted_projected_graph, + projected_graph + + References + ---------- + .. [1] Borgatti, S.P. and Halgin, D. In press. Analyzing Affiliation + Networks. In Carrington, P. and Scott, J. (eds) The Sage Handbook + of Social Network Analysis. Sage Publications. + + """ + if B.is_directed(): + pred = B.pred + G = nx.DiGraph() + else: + pred = B.adj + G = nx.Graph() + G.graph.update(B.graph) + G.add_nodes_from((n, B.nodes[n]) for n in nodes) + for u in nodes: + unbrs = set(B[u]) + nbrs2 = {n for nbr in unbrs for n in B[nbr]} - {u} + for v in nbrs2: + vnbrs = set(pred[v]) + if jaccard: + wt = len(unbrs & vnbrs) / len(unbrs | vnbrs) + else: + wt = len(unbrs & vnbrs) / min(len(unbrs), len(vnbrs)) + G.add_edge(u, v, weight=wt) + return G + + +@not_implemented_for("multigraph") +@nx._dispatchable(graphs="B", preserve_all_attrs=True, returns_graph=True) +def generic_weighted_projected_graph(B, nodes, weight_function=None): + r"""Weighted projection of B with a user-specified weight function. + + The bipartite network B is projected on to the specified nodes + with weights computed by a user-specified function. This function + must accept as a parameter the neighborhood sets of two nodes and + return an integer or a float. + + The nodes retain their attributes and are connected in the resulting graph + if they have an edge to a common node in the original graph. + + Parameters + ---------- + B : NetworkX graph + The input graph should be bipartite. + + nodes : list or iterable + Nodes to project onto (the "bottom" nodes). + + weight_function : function + This function must accept as parameters the same input graph + that this function, and two nodes; and return an integer or a float. + The default function computes the number of shared neighbors. + + Returns + ------- + Graph : NetworkX graph + A graph that is the projection onto the given nodes. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> # Define some custom weight functions + >>> def jaccard(G, u, v): + ... unbrs = set(G[u]) + ... vnbrs = set(G[v]) + ... return float(len(unbrs & vnbrs)) / len(unbrs | vnbrs) + >>> def my_weight(G, u, v, weight="weight"): + ... w = 0 + ... for nbr in set(G[u]) & set(G[v]): + ... w += G[u][nbr].get(weight, 1) + G[v][nbr].get(weight, 1) + ... return w + >>> # A complete bipartite graph with 4 nodes and 4 edges + >>> B = nx.complete_bipartite_graph(2, 2) + >>> # Add some arbitrary weight to the edges + >>> for i, (u, v) in enumerate(B.edges()): + ... B.edges[u, v]["weight"] = i + 1 + >>> for edge in B.edges(data=True): + ... print(edge) + (0, 2, {'weight': 1}) + (0, 3, {'weight': 2}) + (1, 2, {'weight': 3}) + (1, 3, {'weight': 4}) + >>> # By default, the weight is the number of shared neighbors + >>> G = bipartite.generic_weighted_projected_graph(B, [0, 1]) + >>> print(list(G.edges(data=True))) + [(0, 1, {'weight': 2})] + >>> # To specify a custom weight function use the weight_function parameter + >>> G = bipartite.generic_weighted_projected_graph( + ... B, [0, 1], weight_function=jaccard + ... ) + >>> print(list(G.edges(data=True))) + [(0, 1, {'weight': 1.0})] + >>> G = bipartite.generic_weighted_projected_graph( + ... B, [0, 1], weight_function=my_weight + ... ) + >>> print(list(G.edges(data=True))) + [(0, 1, {'weight': 10})] + + Notes + ----- + No attempt is made to verify that the input graph B is bipartite. + The graph and node properties are (shallow) copied to the projected graph. + + See :mod:`bipartite documentation ` + for further details on how bipartite graphs are handled in NetworkX. + + See Also + -------- + is_bipartite, + is_bipartite_node_set, + sets, + weighted_projected_graph, + collaboration_weighted_projected_graph, + overlap_weighted_projected_graph, + projected_graph + + """ + if B.is_directed(): + pred = B.pred + G = nx.DiGraph() + else: + pred = B.adj + G = nx.Graph() + if weight_function is None: + + def weight_function(G, u, v): + # Notice that we use set(pred[v]) for handling the directed case. + return len(set(G[u]) & set(pred[v])) + + G.graph.update(B.graph) + G.add_nodes_from((n, B.nodes[n]) for n in nodes) + for u in nodes: + nbrs2 = {n for nbr in set(B[u]) for n in B[nbr]} - {u} + for v in nbrs2: + weight = weight_function(B, u, v) + G.add_edge(u, v, weight=weight) + return G diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/redundancy.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/redundancy.py new file mode 100644 index 0000000000000000000000000000000000000000..c96f1b58a1b164649ef2f3adce1f5897d6da9dd1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/redundancy.py @@ -0,0 +1,112 @@ +"""Node redundancy for bipartite graphs.""" + +from itertools import combinations + +import networkx as nx +from networkx import NetworkXError + +__all__ = ["node_redundancy"] + + +@nx._dispatchable +def node_redundancy(G, nodes=None): + r"""Computes the node redundancy coefficients for the nodes in the bipartite + graph `G`. + + The redundancy coefficient of a node `v` is the fraction of pairs of + neighbors of `v` that are both linked to other nodes. In a one-mode + projection these nodes would be linked together even if `v` were + not there. + + More formally, for any vertex `v`, the *redundancy coefficient of `v`* is + defined by + + .. math:: + + rc(v) = \frac{|\{\{u, w\} \subseteq N(v), + \: \exists v' \neq v,\: (v',u) \in E\: + \mathrm{and}\: (v',w) \in E\}|}{ \frac{|N(v)|(|N(v)|-1)}{2}}, + + where `N(v)` is the set of neighbors of `v` in `G` [1]_. + + Parameters + ---------- + G : graph + A bipartite graph + + nodes : list or iterable (optional) + Compute redundancy for these nodes. The default is all nodes in G. + + Returns + ------- + redundancy : dictionary + A dictionary keyed by node with the node redundancy value. + + Examples + -------- + Compute the redundancy coefficient of each node in a graph: + + >>> from networkx.algorithms import bipartite + >>> G = nx.cycle_graph(4) + >>> rc = bipartite.node_redundancy(G) + >>> rc[0] + 1.0 + + Compute the average redundancy for the graph: + + >>> from networkx.algorithms import bipartite + >>> G = nx.cycle_graph(4) + >>> rc = bipartite.node_redundancy(G) + >>> sum(rc.values()) / len(G) + 1.0 + + Compute the average redundancy for a set of nodes: + + >>> from networkx.algorithms import bipartite + >>> G = nx.cycle_graph(4) + >>> rc = bipartite.node_redundancy(G) + >>> nodes = [0, 2] + >>> sum(rc[n] for n in nodes) / len(nodes) + 1.0 + + Raises + ------ + NetworkXError + If any of the nodes in the graph (or in `nodes`, if specified) has + (out-)degree less than two (which would result in division by zero, + according to the definition of the redundancy coefficient). + + References + ---------- + .. [1] Latapy, Matthieu, Clémence Magnien, and Nathalie Del Vecchio (2008). + Basic notions for the analysis of large two-mode networks. + Social Networks 30(1), 31--48. + + """ + if nodes is None: + nodes = G + if any(len(G[v]) < 2 for v in nodes): + raise NetworkXError( + "Cannot compute redundancy coefficient for a node" + " that has fewer than two neighbors." + ) + # TODO This can be trivially parallelized. + return {v: _node_redundancy(G, v) for v in nodes} + + +def _node_redundancy(G, v): + """Returns the redundancy of the node `v` in the bipartite graph `G`. + + If `G` is a graph with `n` nodes, the redundancy of a node is the ratio + of the "overlap" of `v` to the maximum possible overlap of `v` + according to its degree. The overlap of `v` is the number of pairs of + neighbors that have mutual neighbors themselves, other than `v`. + + `v` must have at least two neighbors in `G`. + + """ + n = len(G[v]) + overlap = sum( + 1 for (u, w) in combinations(G[v], 2) if (set(G[u]) & set(G[w])) - {v} + ) + return (2 * overlap) / (n * (n - 1)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/spectral.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/spectral.py new file mode 100644 index 0000000000000000000000000000000000000000..cb9388f6cb61cb3c5da865e22449f4e8f2d1e720 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/spectral.py @@ -0,0 +1,69 @@ +""" +Spectral bipartivity measure. +""" + +import networkx as nx + +__all__ = ["spectral_bipartivity"] + + +@nx._dispatchable(edge_attrs="weight") +def spectral_bipartivity(G, nodes=None, weight="weight"): + """Returns the spectral bipartivity. + + Parameters + ---------- + G : NetworkX graph + + nodes : list or container optional(default is all nodes) + Nodes to return value of spectral bipartivity contribution. + + weight : string or None optional (default = 'weight') + Edge data key to use for edge weights. If None, weights set to 1. + + Returns + ------- + sb : float or dict + A single number if the keyword nodes is not specified, or + a dictionary keyed by node with the spectral bipartivity contribution + of that node as the value. + + Examples + -------- + >>> from networkx.algorithms import bipartite + >>> G = nx.path_graph(4) + >>> bipartite.spectral_bipartivity(G) + 1.0 + + Notes + ----- + This implementation uses Numpy (dense) matrices which are not efficient + for storing large sparse graphs. + + See Also + -------- + color + + References + ---------- + .. [1] E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of + bipartivity in complex networks", PhysRev E 72, 046105 (2005) + """ + import scipy as sp + + nodelist = list(G) # ordering of nodes in matrix + A = nx.to_numpy_array(G, nodelist, weight=weight) + expA = sp.linalg.expm(A) + expmA = sp.linalg.expm(-A) + coshA = 0.5 * (expA + expmA) + if nodes is None: + # return single number for entire graph + return float(coshA.diagonal().sum() / expA.diagonal().sum()) + else: + # contribution for individual nodes + index = dict(zip(nodelist, range(len(nodelist)))) + sb = {} + for n in nodes: + i = index[n] + sb[n] = coshA.item(i, i) / expA.item(i, i) + return sb diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06d8a85048ad1be1659e5fe9de63809143672fbb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06100c192d3f34a86cea6b7bc48cc168a1faafa1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_basic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fadd27ce5c97e427126b9b364213663d10327667 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac84301bcbb7d01086014e64ada032bb13fe1f45 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_cluster.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15f2eb0e5a999a33c80589c735f3e3f4ea9488a0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_covering.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cce6f66dd3f3ee3004f378525fbf4ecaf586f8a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_edgelist.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d572c2e66a27dcaadf191cdd87d72cce97d7e422 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_extendability.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be9eecafb75ea64c0a5343f264976d8fe6cb3c42 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_generators.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_link_analysis.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_link_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3651808dd7b58b7cbbbe79973f79b3a6dfa93c2d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_link_analysis.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bc08a3605648adc84e25c65ad39a74d510337a0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9b47533289b3d6031587cd976663b6363e5e942 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_matrix.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..558a400a62275626067549933ed23d31b54aa561 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_project.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3772cb04ec185bc5cdb6fbc86ed1925c5945403 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_redundancy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de284640b95c584bf39b86ba6f2b7171277ece7a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/__pycache__/test_spectral_bipartivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py new file mode 100644 index 0000000000000000000000000000000000000000..655506b4f74110b57cb37db277e2be50bb0be8f4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_basic.py @@ -0,0 +1,125 @@ +import pytest + +import networkx as nx +from networkx.algorithms import bipartite + + +class TestBipartiteBasic: + def test_is_bipartite(self): + assert bipartite.is_bipartite(nx.path_graph(4)) + assert bipartite.is_bipartite(nx.DiGraph([(1, 0)])) + assert not bipartite.is_bipartite(nx.complete_graph(3)) + + def test_bipartite_color(self): + G = nx.path_graph(4) + c = bipartite.color(G) + assert c == {0: 1, 1: 0, 2: 1, 3: 0} + + def test_not_bipartite_color(self): + with pytest.raises(nx.NetworkXError): + c = bipartite.color(nx.complete_graph(4)) + + def test_bipartite_directed(self): + G = bipartite.random_graph(10, 10, 0.1, directed=True) + assert bipartite.is_bipartite(G) + + def test_bipartite_sets(self): + G = nx.path_graph(4) + X, Y = bipartite.sets(G) + assert X == {0, 2} + assert Y == {1, 3} + + def test_bipartite_sets_directed(self): + G = nx.path_graph(4) + D = G.to_directed() + X, Y = bipartite.sets(D) + assert X == {0, 2} + assert Y == {1, 3} + + def test_bipartite_sets_given_top_nodes(self): + G = nx.path_graph(4) + top_nodes = [0, 2] + X, Y = bipartite.sets(G, top_nodes) + assert X == {0, 2} + assert Y == {1, 3} + + def test_bipartite_sets_disconnected(self): + with pytest.raises(nx.AmbiguousSolution): + G = nx.path_graph(4) + G.add_edges_from([(5, 6), (6, 7)]) + X, Y = bipartite.sets(G) + + def test_is_bipartite_node_set(self): + G = nx.path_graph(4) + + with pytest.raises(nx.AmbiguousSolution): + bipartite.is_bipartite_node_set(G, [1, 1, 2, 3]) + + assert bipartite.is_bipartite_node_set(G, [0, 2]) + assert bipartite.is_bipartite_node_set(G, [1, 3]) + assert not bipartite.is_bipartite_node_set(G, [1, 2]) + G.add_edge(10, 20) + assert bipartite.is_bipartite_node_set(G, [0, 2, 10]) + assert bipartite.is_bipartite_node_set(G, [0, 2, 20]) + assert bipartite.is_bipartite_node_set(G, [1, 3, 10]) + assert bipartite.is_bipartite_node_set(G, [1, 3, 20]) + + def test_bipartite_density(self): + G = nx.path_graph(5) + X, Y = bipartite.sets(G) + density = len(list(G.edges())) / (len(X) * len(Y)) + assert bipartite.density(G, X) == density + D = nx.DiGraph(G.edges()) + assert bipartite.density(D, X) == density / 2.0 + assert bipartite.density(nx.Graph(), {}) == 0.0 + + def test_bipartite_degrees(self): + G = nx.path_graph(5) + X = {1, 3} + Y = {0, 2, 4} + u, d = bipartite.degrees(G, Y) + assert dict(u) == {1: 2, 3: 2} + assert dict(d) == {0: 1, 2: 2, 4: 1} + + def test_bipartite_weighted_degrees(self): + G = nx.path_graph(5) + G.add_edge(0, 1, weight=0.1, other=0.2) + X = {1, 3} + Y = {0, 2, 4} + u, d = bipartite.degrees(G, Y, weight="weight") + assert dict(u) == {1: 1.1, 3: 2} + assert dict(d) == {0: 0.1, 2: 2, 4: 1} + u, d = bipartite.degrees(G, Y, weight="other") + assert dict(u) == {1: 1.2, 3: 2} + assert dict(d) == {0: 0.2, 2: 2, 4: 1} + + def test_biadjacency_matrix_weight(self): + pytest.importorskip("scipy") + G = nx.path_graph(5) + G.add_edge(0, 1, weight=2, other=4) + X = [1, 3] + Y = [0, 2, 4] + M = bipartite.biadjacency_matrix(G, X, weight="weight") + assert M[0, 0] == 2 + M = bipartite.biadjacency_matrix(G, X, weight="other") + assert M[0, 0] == 4 + + def test_biadjacency_matrix(self): + pytest.importorskip("scipy") + tops = [2, 5, 10] + bots = [5, 10, 15] + for i in range(len(tops)): + G = bipartite.random_graph(tops[i], bots[i], 0.2) + top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0] + M = bipartite.biadjacency_matrix(G, top) + assert M.shape[0] == tops[i] + assert M.shape[1] == bots[i] + + def test_biadjacency_matrix_order(self): + pytest.importorskip("scipy") + G = nx.path_graph(5) + G.add_edge(0, 1, weight=2) + X = [3, 1] + Y = [4, 2, 0] + M = bipartite.biadjacency_matrix(G, X, Y, weight="weight") + assert M[1, 2] == 2 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..19fb5d117be94c688616a394ea3322e93bfa3e00 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_centrality.py @@ -0,0 +1,192 @@ +import pytest + +import networkx as nx +from networkx.algorithms import bipartite + + +class TestBipartiteCentrality: + @classmethod + def setup_class(cls): + cls.P4 = nx.path_graph(4) + cls.K3 = nx.complete_bipartite_graph(3, 3) + cls.C4 = nx.cycle_graph(4) + cls.davis = nx.davis_southern_women_graph() + cls.top_nodes = [ + n for n, d in cls.davis.nodes(data=True) if d["bipartite"] == 0 + ] + + def test_degree_centrality(self): + d = bipartite.degree_centrality(self.P4, [1, 3]) + answer = {0: 0.5, 1: 1.0, 2: 1.0, 3: 0.5} + assert d == answer + d = bipartite.degree_centrality(self.K3, [0, 1, 2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0} + assert d == answer + d = bipartite.degree_centrality(self.C4, [0, 2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0} + assert d == answer + + def test_betweenness_centrality(self): + c = bipartite.betweenness_centrality(self.P4, [1, 3]) + answer = {0: 0.0, 1: 1.0, 2: 1.0, 3: 0.0} + assert c == answer + c = bipartite.betweenness_centrality(self.K3, [0, 1, 2]) + answer = {0: 0.125, 1: 0.125, 2: 0.125, 3: 0.125, 4: 0.125, 5: 0.125} + assert c == answer + c = bipartite.betweenness_centrality(self.C4, [0, 2]) + answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} + assert c == answer + + def test_closeness_centrality(self): + c = bipartite.closeness_centrality(self.P4, [1, 3]) + answer = {0: 2.0 / 3, 1: 1.0, 2: 1.0, 3: 2.0 / 3} + assert c == answer + c = bipartite.closeness_centrality(self.K3, [0, 1, 2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0, 4: 1.0, 5: 1.0} + assert c == answer + c = bipartite.closeness_centrality(self.C4, [0, 2]) + answer = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0} + assert c == answer + G = nx.Graph() + G.add_node(0) + G.add_node(1) + c = bipartite.closeness_centrality(G, [0]) + assert c == {0: 0.0, 1: 0.0} + c = bipartite.closeness_centrality(G, [1]) + assert c == {0: 0.0, 1: 0.0} + + def test_bipartite_closeness_centrality_unconnected(self): + G = nx.complete_bipartite_graph(3, 3) + G.add_edge(6, 7) + c = bipartite.closeness_centrality(G, [0, 2, 4, 6], normalized=False) + answer = { + 0: 10.0 / 7, + 2: 10.0 / 7, + 4: 10.0 / 7, + 6: 10.0, + 1: 10.0 / 7, + 3: 10.0 / 7, + 5: 10.0 / 7, + 7: 10.0, + } + assert c == answer + + def test_davis_degree_centrality(self): + G = self.davis + deg = bipartite.degree_centrality(G, self.top_nodes) + answer = { + "E8": 0.78, + "E9": 0.67, + "E7": 0.56, + "Nora Fayette": 0.57, + "Evelyn Jefferson": 0.57, + "Theresa Anderson": 0.57, + "E6": 0.44, + "Sylvia Avondale": 0.50, + "Laura Mandeville": 0.50, + "Brenda Rogers": 0.50, + "Katherina Rogers": 0.43, + "E5": 0.44, + "Helen Lloyd": 0.36, + "E3": 0.33, + "Ruth DeSand": 0.29, + "Verne Sanderson": 0.29, + "E12": 0.33, + "Myra Liddel": 0.29, + "E11": 0.22, + "Eleanor Nye": 0.29, + "Frances Anderson": 0.29, + "Pearl Oglethorpe": 0.21, + "E4": 0.22, + "Charlotte McDowd": 0.29, + "E10": 0.28, + "Olivia Carleton": 0.14, + "Flora Price": 0.14, + "E2": 0.17, + "E1": 0.17, + "Dorothy Murchison": 0.14, + "E13": 0.17, + "E14": 0.17, + } + for node, value in answer.items(): + assert value == pytest.approx(deg[node], abs=1e-2) + + def test_davis_betweenness_centrality(self): + G = self.davis + bet = bipartite.betweenness_centrality(G, self.top_nodes) + answer = { + "E8": 0.24, + "E9": 0.23, + "E7": 0.13, + "Nora Fayette": 0.11, + "Evelyn Jefferson": 0.10, + "Theresa Anderson": 0.09, + "E6": 0.07, + "Sylvia Avondale": 0.07, + "Laura Mandeville": 0.05, + "Brenda Rogers": 0.05, + "Katherina Rogers": 0.05, + "E5": 0.04, + "Helen Lloyd": 0.04, + "E3": 0.02, + "Ruth DeSand": 0.02, + "Verne Sanderson": 0.02, + "E12": 0.02, + "Myra Liddel": 0.02, + "E11": 0.02, + "Eleanor Nye": 0.01, + "Frances Anderson": 0.01, + "Pearl Oglethorpe": 0.01, + "E4": 0.01, + "Charlotte McDowd": 0.01, + "E10": 0.01, + "Olivia Carleton": 0.01, + "Flora Price": 0.01, + "E2": 0.00, + "E1": 0.00, + "Dorothy Murchison": 0.00, + "E13": 0.00, + "E14": 0.00, + } + for node, value in answer.items(): + assert value == pytest.approx(bet[node], abs=1e-2) + + def test_davis_closeness_centrality(self): + G = self.davis + clos = bipartite.closeness_centrality(G, self.top_nodes) + answer = { + "E8": 0.85, + "E9": 0.79, + "E7": 0.73, + "Nora Fayette": 0.80, + "Evelyn Jefferson": 0.80, + "Theresa Anderson": 0.80, + "E6": 0.69, + "Sylvia Avondale": 0.77, + "Laura Mandeville": 0.73, + "Brenda Rogers": 0.73, + "Katherina Rogers": 0.73, + "E5": 0.59, + "Helen Lloyd": 0.73, + "E3": 0.56, + "Ruth DeSand": 0.71, + "Verne Sanderson": 0.71, + "E12": 0.56, + "Myra Liddel": 0.69, + "E11": 0.54, + "Eleanor Nye": 0.67, + "Frances Anderson": 0.67, + "Pearl Oglethorpe": 0.67, + "E4": 0.54, + "Charlotte McDowd": 0.60, + "E10": 0.55, + "Olivia Carleton": 0.59, + "Flora Price": 0.59, + "E2": 0.52, + "E1": 0.52, + "Dorothy Murchison": 0.65, + "E13": 0.52, + "E14": 0.52, + } + for node, value in answer.items(): + assert value == pytest.approx(clos[node], abs=1e-2) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py new file mode 100644 index 0000000000000000000000000000000000000000..72e2dbadd64e9e768d1541b2ce742c2b62278929 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_cluster.py @@ -0,0 +1,84 @@ +import pytest + +import networkx as nx +from networkx.algorithms import bipartite +from networkx.algorithms.bipartite.cluster import cc_dot, cc_max, cc_min + + +def test_pairwise_bipartite_cc_functions(): + # Test functions for different kinds of bipartite clustering coefficients + # between pairs of nodes using 3 example graphs from figure 5 p. 40 + # Latapy et al (2008) + G1 = nx.Graph([(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7)]) + G2 = nx.Graph([(0, 2), (0, 3), (0, 4), (1, 3), (1, 4), (1, 5)]) + G3 = nx.Graph( + [(0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9)] + ) + result = { + 0: [1 / 3.0, 2 / 3.0, 2 / 5.0], + 1: [1 / 2.0, 2 / 3.0, 2 / 3.0], + 2: [2 / 8.0, 2 / 5.0, 2 / 5.0], + } + for i, G in enumerate([G1, G2, G3]): + assert bipartite.is_bipartite(G) + assert cc_dot(set(G[0]), set(G[1])) == result[i][0] + assert cc_min(set(G[0]), set(G[1])) == result[i][1] + assert cc_max(set(G[0]), set(G[1])) == result[i][2] + + +def test_star_graph(): + G = nx.star_graph(3) + # all modes are the same + answer = {0: 0, 1: 1, 2: 1, 3: 1} + assert bipartite.clustering(G, mode="dot") == answer + assert bipartite.clustering(G, mode="min") == answer + assert bipartite.clustering(G, mode="max") == answer + + +def test_not_bipartite(): + with pytest.raises(nx.NetworkXError): + bipartite.clustering(nx.complete_graph(4)) + + +def test_bad_mode(): + with pytest.raises(nx.NetworkXError): + bipartite.clustering(nx.path_graph(4), mode="foo") + + +def test_path_graph(): + G = nx.path_graph(4) + answer = {0: 0.5, 1: 0.5, 2: 0.5, 3: 0.5} + assert bipartite.clustering(G, mode="dot") == answer + assert bipartite.clustering(G, mode="max") == answer + answer = {0: 1, 1: 1, 2: 1, 3: 1} + assert bipartite.clustering(G, mode="min") == answer + + +def test_average_path_graph(): + G = nx.path_graph(4) + assert bipartite.average_clustering(G, mode="dot") == 0.5 + assert bipartite.average_clustering(G, mode="max") == 0.5 + assert bipartite.average_clustering(G, mode="min") == 1 + + +def test_ra_clustering_davis(): + G = nx.davis_southern_women_graph() + cc4 = round(bipartite.robins_alexander_clustering(G), 3) + assert cc4 == 0.468 + + +def test_ra_clustering_square(): + G = nx.path_graph(4) + G.add_edge(0, 3) + assert bipartite.robins_alexander_clustering(G) == 1.0 + + +def test_ra_clustering_zero(): + G = nx.Graph() + assert bipartite.robins_alexander_clustering(G) == 0 + G.add_nodes_from(range(4)) + assert bipartite.robins_alexander_clustering(G) == 0 + G.add_edges_from([(0, 1), (2, 3), (3, 4)]) + assert bipartite.robins_alexander_clustering(G) == 0 + G.add_edge(1, 2) + assert bipartite.robins_alexander_clustering(G) == 0 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py new file mode 100644 index 0000000000000000000000000000000000000000..9507e13492acbe505aa3394a24dbc41c095a037c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_covering.py @@ -0,0 +1,33 @@ +import networkx as nx +from networkx.algorithms import bipartite + + +class TestMinEdgeCover: + """Tests for :func:`networkx.algorithms.bipartite.min_edge_cover`""" + + def test_empty_graph(self): + G = nx.Graph() + assert bipartite.min_edge_cover(G) == set() + + def test_graph_single_edge(self): + G = nx.Graph() + G.add_edge(0, 1) + assert bipartite.min_edge_cover(G) == {(0, 1), (1, 0)} + + def test_bipartite_default(self): + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4], bipartite=0) + G.add_nodes_from(["a", "b", "c"], bipartite=1) + G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")]) + min_cover = bipartite.min_edge_cover(G) + assert nx.is_edge_cover(G, min_cover) + assert len(min_cover) == 8 + + def test_bipartite_explicit(self): + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4], bipartite=0) + G.add_nodes_from(["a", "b", "c"], bipartite=1) + G.add_edges_from([(1, "a"), (1, "b"), (2, "b"), (2, "c"), (3, "c"), (4, "a")]) + min_cover = bipartite.min_edge_cover(G, bipartite.eppstein_matching) + assert nx.is_edge_cover(G, min_cover) + assert len(min_cover) == 8 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py new file mode 100644 index 0000000000000000000000000000000000000000..66be8a2f5b3e1f9486594c63015295ad6a270efa --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_edgelist.py @@ -0,0 +1,240 @@ +""" +Unit tests for bipartite edgelists. +""" + +import io + +import pytest + +import networkx as nx +from networkx.algorithms import bipartite +from networkx.utils import edges_equal, graphs_equal, nodes_equal + + +class TestEdgelist: + @classmethod + def setup_class(cls): + cls.G = nx.Graph(name="test") + e = [("a", "b"), ("b", "c"), ("c", "d"), ("d", "e"), ("e", "f"), ("a", "f")] + cls.G.add_edges_from(e) + cls.G.add_nodes_from(["a", "c", "e"], bipartite=0) + cls.G.add_nodes_from(["b", "d", "f"], bipartite=1) + cls.G.add_node("g", bipartite=0) + cls.DG = nx.DiGraph(cls.G) + cls.MG = nx.MultiGraph() + cls.MG.add_edges_from([(1, 2), (1, 2), (1, 2)]) + cls.MG.add_node(1, bipartite=0) + cls.MG.add_node(2, bipartite=1) + + def test_read_edgelist_1(self): + s = b"""\ +# comment line +1 2 +# comment line +2 3 +""" + bytesIO = io.BytesIO(s) + G = bipartite.read_edgelist(bytesIO, nodetype=int) + assert edges_equal(G.edges(), [(1, 2), (2, 3)]) + + def test_read_edgelist_3(self): + s = b"""\ +# comment line +1 2 {'weight':2.0} +# comment line +2 3 {'weight':3.0} +""" + bytesIO = io.BytesIO(s) + G = bipartite.read_edgelist(bytesIO, nodetype=int, data=False) + assert edges_equal(G.edges(), [(1, 2), (2, 3)]) + + bytesIO = io.BytesIO(s) + G = bipartite.read_edgelist(bytesIO, nodetype=int, data=True) + assert edges_equal( + G.edges(data=True), [(1, 2, {"weight": 2.0}), (2, 3, {"weight": 3.0})] + ) + + def test_write_edgelist_1(self): + fh = io.BytesIO() + G = nx.Graph() + G.add_edges_from([(1, 2), (2, 3)]) + G.add_node(1, bipartite=0) + G.add_node(2, bipartite=1) + G.add_node(3, bipartite=0) + bipartite.write_edgelist(G, fh, data=False) + fh.seek(0) + assert fh.read() == b"1 2\n3 2\n" + + def test_write_edgelist_2(self): + fh = io.BytesIO() + G = nx.Graph() + G.add_edges_from([(1, 2), (2, 3)]) + G.add_node(1, bipartite=0) + G.add_node(2, bipartite=1) + G.add_node(3, bipartite=0) + bipartite.write_edgelist(G, fh, data=True) + fh.seek(0) + assert fh.read() == b"1 2 {}\n3 2 {}\n" + + def test_write_edgelist_3(self): + fh = io.BytesIO() + G = nx.Graph() + G.add_edge(1, 2, weight=2.0) + G.add_edge(2, 3, weight=3.0) + G.add_node(1, bipartite=0) + G.add_node(2, bipartite=1) + G.add_node(3, bipartite=0) + bipartite.write_edgelist(G, fh, data=True) + fh.seek(0) + assert fh.read() == b"1 2 {'weight': 2.0}\n3 2 {'weight': 3.0}\n" + + def test_write_edgelist_4(self): + fh = io.BytesIO() + G = nx.Graph() + G.add_edge(1, 2, weight=2.0) + G.add_edge(2, 3, weight=3.0) + G.add_node(1, bipartite=0) + G.add_node(2, bipartite=1) + G.add_node(3, bipartite=0) + bipartite.write_edgelist(G, fh, data=[("weight")]) + fh.seek(0) + assert fh.read() == b"1 2 2.0\n3 2 3.0\n" + + def test_unicode(self, tmp_path): + G = nx.Graph() + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + G.add_edge(name1, "Radiohead", **{name2: 3}) + G.add_node(name1, bipartite=0) + G.add_node("Radiohead", bipartite=1) + + fname = tmp_path / "edgelist.txt" + bipartite.write_edgelist(G, fname) + H = bipartite.read_edgelist(fname) + assert graphs_equal(G, H) + + def test_latin1_issue(self, tmp_path): + G = nx.Graph() + name1 = chr(2344) + chr(123) + chr(6543) + name2 = chr(5543) + chr(1543) + chr(324) + G.add_edge(name1, "Radiohead", **{name2: 3}) + G.add_node(name1, bipartite=0) + G.add_node("Radiohead", bipartite=1) + + fname = tmp_path / "edgelist.txt" + with pytest.raises(UnicodeEncodeError): + bipartite.write_edgelist(G, fname, encoding="latin-1") + + def test_latin1(self, tmp_path): + G = nx.Graph() + name1 = "Bj" + chr(246) + "rk" + name2 = chr(220) + "ber" + G.add_edge(name1, "Radiohead", **{name2: 3}) + G.add_node(name1, bipartite=0) + G.add_node("Radiohead", bipartite=1) + + fname = tmp_path / "edgelist.txt" + bipartite.write_edgelist(G, fname, encoding="latin-1") + H = bipartite.read_edgelist(fname, encoding="latin-1") + assert graphs_equal(G, H) + + def test_edgelist_graph(self, tmp_path): + G = self.G + fname = tmp_path / "edgelist.txt" + bipartite.write_edgelist(G, fname) + H = bipartite.read_edgelist(fname) + H2 = bipartite.read_edgelist(fname) + assert H is not H2 # they should be different graphs + G.remove_node("g") # isolated nodes are not written in edgelist + assert nodes_equal(list(H), list(G)) + assert edges_equal(list(H.edges()), list(G.edges())) + + def test_edgelist_integers(self, tmp_path): + G = nx.convert_node_labels_to_integers(self.G) + fname = tmp_path / "edgelist.txt" + bipartite.write_edgelist(G, fname) + H = bipartite.read_edgelist(fname, nodetype=int) + # isolated nodes are not written in edgelist + G.remove_nodes_from(list(nx.isolates(G))) + assert nodes_equal(list(H), list(G)) + assert edges_equal(list(H.edges()), list(G.edges())) + + def test_edgelist_multigraph(self, tmp_path): + G = self.MG + fname = tmp_path / "edgelist.txt" + bipartite.write_edgelist(G, fname) + H = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph()) + H2 = bipartite.read_edgelist(fname, nodetype=int, create_using=nx.MultiGraph()) + assert H is not H2 # they should be different graphs + assert nodes_equal(list(H), list(G)) + assert edges_equal(list(H.edges()), list(G.edges())) + + def test_empty_digraph(self): + with pytest.raises(nx.NetworkXNotImplemented): + bytesIO = io.BytesIO() + bipartite.write_edgelist(nx.DiGraph(), bytesIO) + + def test_raise_attribute(self): + with pytest.raises(AttributeError): + G = nx.path_graph(4) + bytesIO = io.BytesIO() + bipartite.write_edgelist(G, bytesIO) + + def test_parse_edgelist(self): + """Tests for conditions specific to + parse_edge_list method""" + + # ignore strings of length less than 2 + lines = ["1 2", "2 3", "3 1", "4", " "] + G = bipartite.parse_edgelist(lines, nodetype=int) + assert list(G.nodes) == [1, 2, 3] + + # Exception raised when node is not convertible + # to specified data type + with pytest.raises(TypeError, match=".*Failed to convert nodes"): + lines = ["a b", "b c", "c a"] + G = bipartite.parse_edgelist(lines, nodetype=int) + + # Exception raised when format of data is not + # convertible to dictionary object + with pytest.raises(TypeError, match=".*Failed to convert edge data"): + lines = ["1 2 3", "2 3 4", "3 1 2"] + G = bipartite.parse_edgelist(lines, nodetype=int) + + # Exception raised when edge data and data + # keys are not of same length + with pytest.raises(IndexError): + lines = ["1 2 3 4", "2 3 4"] + G = bipartite.parse_edgelist( + lines, nodetype=int, data=[("weight", int), ("key", int)] + ) + + # Exception raised when edge data is not + # convertible to specified data type + with pytest.raises(TypeError, match=".*Failed to convert key data"): + lines = ["1 2 3 a", "2 3 4 b"] + G = bipartite.parse_edgelist( + lines, nodetype=int, data=[("weight", int), ("key", int)] + ) + + +def test_bipartite_edgelist_consistent_strip_handling(): + """See gh-7462 + + Input when printed looks like: + + A B interaction 2 + B C interaction 4 + C A interaction + + Note the trailing \\t in the last line, which indicates the existence of + an empty data field. + """ + lines = io.StringIO( + "A\tB\tinteraction\t2\nB\tC\tinteraction\t4\nC\tA\tinteraction\t" + ) + descr = [("type", str), ("weight", str)] + # Should not raise + G = nx.bipartite.parse_edgelist(lines, delimiter="\t", data=descr) + expected = [("A", "B", "2"), ("A", "C", ""), ("B", "C", "4")] + assert sorted(G.edges(data="weight")) == expected diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py new file mode 100644 index 0000000000000000000000000000000000000000..17b7124341bd6b0e82b5f01b8e5c6f8d1235efb9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_extendability.py @@ -0,0 +1,334 @@ +import pytest + +import networkx as nx + + +def test_selfloops_raises(): + G = nx.ladder_graph(3) + G.add_edge(0, 0) + with pytest.raises(nx.NetworkXError, match=".*not bipartite"): + nx.bipartite.maximal_extendability(G) + + +def test_disconnected_raises(): + G = nx.ladder_graph(3) + G.add_node("a") + with pytest.raises(nx.NetworkXError, match=".*not connected"): + nx.bipartite.maximal_extendability(G) + + +def test_not_bipartite_raises(): + G = nx.complete_graph(5) + with pytest.raises(nx.NetworkXError, match=".*not bipartite"): + nx.bipartite.maximal_extendability(G) + + +def test_no_perfect_matching_raises(): + G = nx.Graph([(0, 1), (0, 2)]) + with pytest.raises(nx.NetworkXError, match=".*not contain a perfect matching"): + nx.bipartite.maximal_extendability(G) + + +def test_residual_graph_not_strongly_connected_raises(): + G = nx.Graph([(1, 2), (2, 3), (3, 4)]) + with pytest.raises( + nx.NetworkXError, match="The residual graph of G is not strongly connected" + ): + nx.bipartite.maximal_extendability(G) + + +def test_ladder_graph_is_1(): + G = nx.ladder_graph(3) + assert nx.bipartite.maximal_extendability(G) == 1 + + +def test_cubical_graph_is_2(): + G = nx.cubical_graph() + assert nx.bipartite.maximal_extendability(G) == 2 + + +def test_k_is_3(): + G = nx.Graph( + [ + (1, 6), + (1, 7), + (1, 8), + (1, 9), + (2, 6), + (2, 7), + (2, 8), + (2, 10), + (3, 6), + (3, 8), + (3, 9), + (3, 10), + (4, 7), + (4, 8), + (4, 9), + (4, 10), + (5, 6), + (5, 7), + (5, 9), + (5, 10), + ] + ) + assert nx.bipartite.maximal_extendability(G) == 3 + + +def test_k_is_4(): + G = nx.Graph( + [ + (8, 1), + (8, 2), + (8, 3), + (8, 4), + (8, 5), + (9, 1), + (9, 2), + (9, 3), + (9, 4), + (9, 7), + (10, 1), + (10, 2), + (10, 3), + (10, 4), + (10, 6), + (11, 1), + (11, 2), + (11, 5), + (11, 6), + (11, 7), + (12, 1), + (12, 3), + (12, 5), + (12, 6), + (12, 7), + (13, 2), + (13, 4), + (13, 5), + (13, 6), + (13, 7), + (14, 3), + (14, 4), + (14, 5), + (14, 6), + (14, 7), + ] + ) + assert nx.bipartite.maximal_extendability(G) == 4 + + +def test_k_is_5(): + G = nx.Graph( + [ + (8, 1), + (8, 2), + (8, 3), + (8, 4), + (8, 5), + (8, 6), + (9, 1), + (9, 2), + (9, 3), + (9, 4), + (9, 5), + (9, 7), + (10, 1), + (10, 2), + (10, 3), + (10, 4), + (10, 6), + (10, 7), + (11, 1), + (11, 2), + (11, 3), + (11, 5), + (11, 6), + (11, 7), + (12, 1), + (12, 2), + (12, 4), + (12, 5), + (12, 6), + (12, 7), + (13, 1), + (13, 3), + (13, 4), + (13, 5), + (13, 6), + (13, 7), + (14, 2), + (14, 3), + (14, 4), + (14, 5), + (14, 6), + (14, 7), + ] + ) + assert nx.bipartite.maximal_extendability(G) == 5 + + +def test_k_is_6(): + G = nx.Graph( + [ + (9, 1), + (9, 2), + (9, 3), + (9, 4), + (9, 5), + (9, 6), + (9, 7), + (10, 1), + (10, 2), + (10, 3), + (10, 4), + (10, 5), + (10, 6), + (10, 8), + (11, 1), + (11, 2), + (11, 3), + (11, 4), + (11, 5), + (11, 7), + (11, 8), + (12, 1), + (12, 2), + (12, 3), + (12, 4), + (12, 6), + (12, 7), + (12, 8), + (13, 1), + (13, 2), + (13, 3), + (13, 5), + (13, 6), + (13, 7), + (13, 8), + (14, 1), + (14, 2), + (14, 4), + (14, 5), + (14, 6), + (14, 7), + (14, 8), + (15, 1), + (15, 3), + (15, 4), + (15, 5), + (15, 6), + (15, 7), + (15, 8), + (16, 2), + (16, 3), + (16, 4), + (16, 5), + (16, 6), + (16, 7), + (16, 8), + ] + ) + assert nx.bipartite.maximal_extendability(G) == 6 + + +def test_k_is_7(): + G = nx.Graph( + [ + (1, 11), + (1, 12), + (1, 13), + (1, 14), + (1, 15), + (1, 16), + (1, 17), + (1, 18), + (2, 11), + (2, 12), + (2, 13), + (2, 14), + (2, 15), + (2, 16), + (2, 17), + (2, 19), + (3, 11), + (3, 12), + (3, 13), + (3, 14), + (3, 15), + (3, 16), + (3, 17), + (3, 20), + (4, 11), + (4, 12), + (4, 13), + (4, 14), + (4, 15), + (4, 16), + (4, 17), + (4, 18), + (4, 19), + (4, 20), + (5, 11), + (5, 12), + (5, 13), + (5, 14), + (5, 15), + (5, 16), + (5, 17), + (5, 18), + (5, 19), + (5, 20), + (6, 11), + (6, 12), + (6, 13), + (6, 14), + (6, 15), + (6, 16), + (6, 17), + (6, 18), + (6, 19), + (6, 20), + (7, 11), + (7, 12), + (7, 13), + (7, 14), + (7, 15), + (7, 16), + (7, 17), + (7, 18), + (7, 19), + (7, 20), + (8, 11), + (8, 12), + (8, 13), + (8, 14), + (8, 15), + (8, 16), + (8, 17), + (8, 18), + (8, 19), + (8, 20), + (9, 11), + (9, 12), + (9, 13), + (9, 14), + (9, 15), + (9, 16), + (9, 17), + (9, 18), + (9, 19), + (9, 20), + (10, 11), + (10, 12), + (10, 13), + (10, 14), + (10, 15), + (10, 16), + (10, 17), + (10, 18), + (10, 19), + (10, 20), + ] + ) + assert nx.bipartite.maximal_extendability(G) == 7 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py new file mode 100644 index 0000000000000000000000000000000000000000..3c394db66e651adfc4382e1e8e97aba6f31ac495 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_generators.py @@ -0,0 +1,407 @@ +import numbers + +import pytest + +import networkx as nx + +from ..generators import ( + alternating_havel_hakimi_graph, + complete_bipartite_graph, + configuration_model, + gnmk_random_graph, + havel_hakimi_graph, + preferential_attachment_graph, + random_graph, + reverse_havel_hakimi_graph, +) + +""" +Generators - Bipartite +---------------------- +""" + + +class TestGeneratorsBipartite: + def test_complete_bipartite_graph(self): + G = complete_bipartite_graph(0, 0) + assert nx.is_isomorphic(G, nx.null_graph()) + + for i in [1, 5]: + G = complete_bipartite_graph(i, 0) + assert nx.is_isomorphic(G, nx.empty_graph(i)) + G = complete_bipartite_graph(0, i) + assert nx.is_isomorphic(G, nx.empty_graph(i)) + + G = complete_bipartite_graph(2, 2) + assert nx.is_isomorphic(G, nx.cycle_graph(4)) + + G = complete_bipartite_graph(1, 5) + assert nx.is_isomorphic(G, nx.star_graph(5)) + + G = complete_bipartite_graph(5, 1) + assert nx.is_isomorphic(G, nx.star_graph(5)) + + # complete_bipartite_graph(m1,m2) is a connected graph with + # m1+m2 nodes and m1*m2 edges + for m1, m2 in [(5, 11), (7, 3)]: + G = complete_bipartite_graph(m1, m2) + assert nx.number_of_nodes(G) == m1 + m2 + assert nx.number_of_edges(G) == m1 * m2 + + with pytest.raises(nx.NetworkXError): + complete_bipartite_graph(7, 3, create_using=nx.DiGraph) + with pytest.raises(nx.NetworkXError): + complete_bipartite_graph(7, 3, create_using=nx.MultiDiGraph) + + mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph) + assert mG.is_multigraph() + assert sorted(mG.edges()) == sorted(G.edges()) + + mG = complete_bipartite_graph(7, 3, create_using=nx.MultiGraph) + assert mG.is_multigraph() + assert sorted(mG.edges()) == sorted(G.edges()) + + mG = complete_bipartite_graph(7, 3) # default to Graph + assert sorted(mG.edges()) == sorted(G.edges()) + assert not mG.is_multigraph() + assert not mG.is_directed() + + # specify nodes rather than number of nodes + for n1, n2 in [([1, 2], "ab"), (3, 2), (3, "ab"), ("ab", 3)]: + G = complete_bipartite_graph(n1, n2) + if isinstance(n1, numbers.Integral): + if isinstance(n2, numbers.Integral): + n2 = range(n1, n1 + n2) + n1 = range(n1) + elif isinstance(n2, numbers.Integral): + n2 = range(n2) + edges = {(u, v) for u in n1 for v in n2} + assert edges == set(G.edges) + assert G.size() == len(edges) + + # raise when node sets are not distinct + for n1, n2 in [([1, 2], 3), (3, [1, 2]), ("abc", "bcd")]: + pytest.raises(nx.NetworkXError, complete_bipartite_graph, n1, n2) + + def test_configuration_model(self): + aseq = [] + bseq = [] + G = configuration_model(aseq, bseq) + assert len(G) == 0 + + aseq = [0, 0] + bseq = [0, 0] + G = configuration_model(aseq, bseq) + assert len(G) == 4 + assert G.number_of_edges() == 0 + + aseq = [3, 3, 3, 3] + bseq = [2, 2, 2, 2, 2] + pytest.raises(nx.NetworkXError, configuration_model, aseq, bseq) + + aseq = [3, 3, 3, 3] + bseq = [2, 2, 2, 2, 2, 2] + G = configuration_model(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 2, 2, 2] + bseq = [3, 3, 3, 3] + G = configuration_model(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 1, 1, 1] + bseq = [3, 3, 3] + G = configuration_model(aseq, bseq) + assert G.is_multigraph() + assert not G.is_directed() + assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3] + + GU = nx.projected_graph(nx.Graph(G), range(len(aseq))) + assert GU.number_of_nodes() == 6 + + GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq))) + assert GD.number_of_nodes() == 3 + + G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph) + assert not G.is_multigraph() + assert not G.is_directed() + + pytest.raises( + nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph() + ) + pytest.raises( + nx.NetworkXError, configuration_model, aseq, bseq, create_using=nx.DiGraph + ) + pytest.raises( + nx.NetworkXError, + configuration_model, + aseq, + bseq, + create_using=nx.MultiDiGraph, + ) + + def test_havel_hakimi_graph(self): + aseq = [] + bseq = [] + G = havel_hakimi_graph(aseq, bseq) + assert len(G) == 0 + + aseq = [0, 0] + bseq = [0, 0] + G = havel_hakimi_graph(aseq, bseq) + assert len(G) == 4 + assert G.number_of_edges() == 0 + + aseq = [3, 3, 3, 3] + bseq = [2, 2, 2, 2, 2] + pytest.raises(nx.NetworkXError, havel_hakimi_graph, aseq, bseq) + + bseq = [2, 2, 2, 2, 2, 2] + G = havel_hakimi_graph(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 2, 2, 2] + bseq = [3, 3, 3, 3] + G = havel_hakimi_graph(aseq, bseq) + assert G.is_multigraph() + assert not G.is_directed() + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + GU = nx.projected_graph(nx.Graph(G), range(len(aseq))) + assert GU.number_of_nodes() == 6 + + GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq))) + assert GD.number_of_nodes() == 4 + + G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph) + assert not G.is_multigraph() + assert not G.is_directed() + + pytest.raises( + nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph + ) + pytest.raises( + nx.NetworkXError, havel_hakimi_graph, aseq, bseq, create_using=nx.DiGraph + ) + pytest.raises( + nx.NetworkXError, + havel_hakimi_graph, + aseq, + bseq, + create_using=nx.MultiDiGraph, + ) + + def test_reverse_havel_hakimi_graph(self): + aseq = [] + bseq = [] + G = reverse_havel_hakimi_graph(aseq, bseq) + assert len(G) == 0 + + aseq = [0, 0] + bseq = [0, 0] + G = reverse_havel_hakimi_graph(aseq, bseq) + assert len(G) == 4 + assert G.number_of_edges() == 0 + + aseq = [3, 3, 3, 3] + bseq = [2, 2, 2, 2, 2] + pytest.raises(nx.NetworkXError, reverse_havel_hakimi_graph, aseq, bseq) + + bseq = [2, 2, 2, 2, 2, 2] + G = reverse_havel_hakimi_graph(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 2, 2, 2] + bseq = [3, 3, 3, 3] + G = reverse_havel_hakimi_graph(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 1, 1, 1] + bseq = [3, 3, 3] + G = reverse_havel_hakimi_graph(aseq, bseq) + assert G.is_multigraph() + assert not G.is_directed() + assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3] + + GU = nx.projected_graph(nx.Graph(G), range(len(aseq))) + assert GU.number_of_nodes() == 6 + + GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq))) + assert GD.number_of_nodes() == 3 + + G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph) + assert not G.is_multigraph() + assert not G.is_directed() + + pytest.raises( + nx.NetworkXError, + reverse_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.DiGraph, + ) + pytest.raises( + nx.NetworkXError, + reverse_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.DiGraph, + ) + pytest.raises( + nx.NetworkXError, + reverse_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.MultiDiGraph, + ) + + def test_alternating_havel_hakimi_graph(self): + aseq = [] + bseq = [] + G = alternating_havel_hakimi_graph(aseq, bseq) + assert len(G) == 0 + + aseq = [0, 0] + bseq = [0, 0] + G = alternating_havel_hakimi_graph(aseq, bseq) + assert len(G) == 4 + assert G.number_of_edges() == 0 + + aseq = [3, 3, 3, 3] + bseq = [2, 2, 2, 2, 2] + pytest.raises(nx.NetworkXError, alternating_havel_hakimi_graph, aseq, bseq) + + bseq = [2, 2, 2, 2, 2, 2] + G = alternating_havel_hakimi_graph(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 2, 2, 2] + bseq = [3, 3, 3, 3] + G = alternating_havel_hakimi_graph(aseq, bseq) + assert sorted(d for n, d in G.degree()) == [2, 2, 2, 2, 2, 2, 3, 3, 3, 3] + + aseq = [2, 2, 2, 1, 1, 1] + bseq = [3, 3, 3] + G = alternating_havel_hakimi_graph(aseq, bseq) + assert G.is_multigraph() + assert not G.is_directed() + assert sorted(d for n, d in G.degree()) == [1, 1, 1, 2, 2, 2, 3, 3, 3] + + GU = nx.projected_graph(nx.Graph(G), range(len(aseq))) + assert GU.number_of_nodes() == 6 + + GD = nx.projected_graph(nx.Graph(G), range(len(aseq), len(aseq) + len(bseq))) + assert GD.number_of_nodes() == 3 + + G = reverse_havel_hakimi_graph(aseq, bseq, create_using=nx.Graph) + assert not G.is_multigraph() + assert not G.is_directed() + + pytest.raises( + nx.NetworkXError, + alternating_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.DiGraph, + ) + pytest.raises( + nx.NetworkXError, + alternating_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.DiGraph, + ) + pytest.raises( + nx.NetworkXError, + alternating_havel_hakimi_graph, + aseq, + bseq, + create_using=nx.MultiDiGraph, + ) + + def test_preferential_attachment(self): + aseq = [3, 2, 1, 1] + G = preferential_attachment_graph(aseq, 0.5) + assert G.is_multigraph() + assert not G.is_directed() + + G = preferential_attachment_graph(aseq, 0.5, create_using=nx.Graph) + assert not G.is_multigraph() + assert not G.is_directed() + + pytest.raises( + nx.NetworkXError, + preferential_attachment_graph, + aseq, + 0.5, + create_using=nx.DiGraph(), + ) + pytest.raises( + nx.NetworkXError, + preferential_attachment_graph, + aseq, + 0.5, + create_using=nx.DiGraph(), + ) + pytest.raises( + nx.NetworkXError, + preferential_attachment_graph, + aseq, + 0.5, + create_using=nx.DiGraph(), + ) + + def test_random_graph(self): + n = 10 + m = 20 + G = random_graph(n, m, 0.9) + assert len(G) == 30 + assert nx.is_bipartite(G) + X, Y = nx.algorithms.bipartite.sets(G) + assert set(range(n)) == X + assert set(range(n, n + m)) == Y + + def test_random_digraph(self): + n = 10 + m = 20 + G = random_graph(n, m, 0.9, directed=True) + assert len(G) == 30 + assert nx.is_bipartite(G) + X, Y = nx.algorithms.bipartite.sets(G) + assert set(range(n)) == X + assert set(range(n, n + m)) == Y + + def test_gnmk_random_graph(self): + n = 10 + m = 20 + edges = 100 + # set seed because sometimes it is not connected + # which raises an error in bipartite.sets(G) below. + G = gnmk_random_graph(n, m, edges, seed=1234) + assert len(G) == n + m + assert nx.is_bipartite(G) + X, Y = nx.algorithms.bipartite.sets(G) + assert set(range(n)) == X + assert set(range(n, n + m)) == Y + assert edges == len(list(G.edges())) + + def test_gnmk_random_graph_complete(self): + n = 10 + m = 20 + edges = 200 + G = gnmk_random_graph(n, m, edges) + assert len(G) == n + m + assert nx.is_bipartite(G) + X, Y = nx.algorithms.bipartite.sets(G) + assert set(range(n)) == X + assert set(range(n, n + m)) == Y + assert edges == len(list(G.edges())) + + @pytest.mark.parametrize("n", (4, range(4), {0, 1, 2, 3})) + @pytest.mark.parametrize("m", (range(4, 7), {4, 5, 6})) + def test_complete_bipartite_graph_str(self, n, m): + """Ensure G.name is consistent for all inputs accepted by nodes_or_number. + See gh-7396""" + G = nx.complete_bipartite_graph(n, m) + ans = "Graph named 'complete_bipartite_graph(4, 3)' with 7 nodes and 12 edges" + assert str(G) == ans diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_link_analysis.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_link_analysis.py new file mode 100644 index 0000000000000000000000000000000000000000..6e46056eeb0c47fc6fd8ac333e1bcf1f000d6c56 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_link_analysis.py @@ -0,0 +1,218 @@ +import itertools + +import pytest + +import networkx as nx +from networkx.algorithms import bipartite + +pytest.importorskip("scipy") + + +class TestBipartiteLinkAnalysis: + @classmethod + def setup_class(cls): + cls.davis_southern_women_graph = nx.davis_southern_women_graph() + cls.women_bipartite_set = { + node + for node, bipartite in cls.davis_southern_women_graph.nodes( + data="bipartite" + ) + if bipartite == 0 + } + cls.gnmk_random_graph = nx.bipartite.generators.gnmk_random_graph( + 5 * 10**2, 10**2, 5 * 10**2, seed=27 + ) + cls.gnmk_random_graph_top_nodes = { + node + for node, bipartite in cls.gnmk_random_graph.nodes(data="bipartite") + if bipartite == 0 + } + + def test_collaborative_filtering_birank(self): + elist = [ + ("u1", "p1", 5), + ("u2", "p1", 5), + ("u2", "p2", 4), + ("u3", "p1", 3), + ("u3", "p3", 2), + ] + item_recommendation_graph = nx.DiGraph() + item_recommendation_graph.add_weighted_edges_from(elist, weight="rating") + product_nodes = ("p1", "p2", "p3") + u1_query = { + product: rating + for _, product, rating in item_recommendation_graph.edges( + nbunch="u1", data="rating" + ) + } + u1_birank_results = bipartite.birank( + item_recommendation_graph, + product_nodes, + alpha=0.8, + beta=1.0, + top_personalization=u1_query, + weight="rating", + ) + + assert u1_birank_results["p2"] > u1_birank_results["p3"] + + u1_birank_results_unweighted = bipartite.birank( + item_recommendation_graph, + product_nodes, + alpha=0.8, + beta=1.0, + top_personalization=u1_query, + weight=None, + ) + + assert u1_birank_results_unweighted["p2"] == pytest.approx( + u1_birank_results_unweighted["p3"], rel=2e-6 + ) + + def test_davis_birank(self): + scores = bipartite.birank( + self.davis_southern_women_graph, self.women_bipartite_set + ) + answer = { + "Laura Mandeville": 0.07, + "Olivia Carleton": 0.04, + "Frances Anderson": 0.05, + "Pearl Oglethorpe": 0.04, + "Katherina Rogers": 0.06, + "Flora Price": 0.04, + "Dorothy Murchison": 0.04, + "Helen Lloyd": 0.06, + "Theresa Anderson": 0.07, + "Eleanor Nye": 0.05, + "Evelyn Jefferson": 0.07, + "Sylvia Avondale": 0.07, + "Charlotte McDowd": 0.05, + "Verne Sanderson": 0.05, + "Myra Liddel": 0.05, + "Brenda Rogers": 0.07, + "Ruth DeSand": 0.05, + "Nora Fayette": 0.07, + "E8": 0.11, + "E7": 0.09, + "E10": 0.07, + "E9": 0.1, + "E13": 0.05, + "E3": 0.07, + "E12": 0.07, + "E11": 0.06, + "E2": 0.05, + "E5": 0.08, + "E6": 0.08, + "E14": 0.05, + "E4": 0.06, + "E1": 0.05, + } + + for node, value in answer.items(): + assert scores[node] == pytest.approx(value, abs=1e-2) + + def test_davis_birank_with_personalization(self): + women_personalization = {"Laura Mandeville": 1} + scores = bipartite.birank( + self.davis_southern_women_graph, + self.women_bipartite_set, + top_personalization=women_personalization, + ) + answer = { + "Laura Mandeville": 0.29, + "Olivia Carleton": 0.02, + "Frances Anderson": 0.06, + "Pearl Oglethorpe": 0.04, + "Katherina Rogers": 0.04, + "Flora Price": 0.02, + "Dorothy Murchison": 0.03, + "Helen Lloyd": 0.04, + "Theresa Anderson": 0.08, + "Eleanor Nye": 0.05, + "Evelyn Jefferson": 0.09, + "Sylvia Avondale": 0.05, + "Charlotte McDowd": 0.06, + "Verne Sanderson": 0.04, + "Myra Liddel": 0.03, + "Brenda Rogers": 0.08, + "Ruth DeSand": 0.05, + "Nora Fayette": 0.05, + "E8": 0.11, + "E7": 0.1, + "E10": 0.04, + "E9": 0.07, + "E13": 0.03, + "E3": 0.11, + "E12": 0.04, + "E11": 0.03, + "E2": 0.1, + "E5": 0.11, + "E6": 0.1, + "E14": 0.03, + "E4": 0.06, + "E1": 0.1, + } + + for node, value in answer.items(): + assert scores[node] == pytest.approx(value, abs=1e-2) + + def test_birank_empty_bipartite_set(self): + G = nx.Graph() + all_nodes = [1, 2, 3] + G.add_nodes_from(all_nodes) + + # Test with empty bipartite set + with pytest.raises(nx.NetworkXAlgorithmError): + bipartite.birank(G, all_nodes) + + @pytest.mark.parametrize( + "damping_factor,value", itertools.product(["alpha", "beta"], [-0.1, 1.1]) + ) + def test_birank_invalid_alpha_beta(self, damping_factor, value): + kwargs = {damping_factor: value} + with pytest.raises(nx.NetworkXAlgorithmError): + bipartite.birank( + self.davis_southern_women_graph, self.women_bipartite_set, **kwargs + ) + + def test_birank_power_iteration_failed_convergence(self): + with pytest.raises(nx.PowerIterationFailedConvergence): + bipartite.birank( + self.davis_southern_women_graph, self.women_bipartite_set, max_iter=1 + ) + + @pytest.mark.parametrize( + "personalization,alpha,beta", + itertools.product( + [ + # Concentrated case + lambda x: 1000 if x == 0 else 0, + # Uniform case + lambda x: 5, + # Zero case + lambda x: 0, + ], + [i / 2 for i in range(3)], + [i / 2 for i in range(3)], + ), + ) + def test_gnmk_convergence_birank(self, personalization, alpha, beta): + top_personalization_dict = { + node: personalization(node) for node in self.gnmk_random_graph_top_nodes + } + bipartite.birank( + self.gnmk_random_graph, + self.gnmk_random_graph_top_nodes, + top_personalization=top_personalization_dict, + alpha=alpha, + beta=beta, + ) + + def test_negative_personalization(self): + top_personalization_dict = {0: -1} + with pytest.raises(nx.NetworkXAlgorithmError): + bipartite.birank( + self.gnmk_random_graph, + self.gnmk_random_graph_top_nodes, + top_personalization=top_personalization_dict, + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py new file mode 100644 index 0000000000000000000000000000000000000000..c24659ea8fcf01ab4a26a6c6959c4935ab9aad2d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matching.py @@ -0,0 +1,327 @@ +"""Unit tests for the :mod:`networkx.algorithms.bipartite.matching` module.""" + +import itertools + +import pytest + +import networkx as nx +from networkx.algorithms.bipartite.matching import ( + eppstein_matching, + hopcroft_karp_matching, + maximum_matching, + minimum_weight_full_matching, + to_vertex_cover, +) + + +class TestMatching: + """Tests for bipartite matching algorithms.""" + + def setup_method(self): + """Creates a bipartite graph for use in testing matching algorithms. + + The bipartite graph has a maximum cardinality matching that leaves + vertex 1 and vertex 10 unmatched. The first six numbers are the left + vertices and the next six numbers are the right vertices. + + """ + self.simple_graph = nx.complete_bipartite_graph(2, 3) + self.simple_solution = {0: 2, 1: 3, 2: 0, 3: 1} + + edges = [(0, 7), (0, 8), (2, 6), (2, 9), (3, 8), (4, 8), (4, 9), (5, 11)] + self.top_nodes = set(range(6)) + self.graph = nx.Graph() + self.graph.add_nodes_from(range(12)) + self.graph.add_edges_from(edges) + + # Example bipartite graph from issue 2127 + G = nx.Graph() + G.add_nodes_from( + [ + (1, "C"), + (1, "B"), + (0, "G"), + (1, "F"), + (1, "E"), + (0, "C"), + (1, "D"), + (1, "I"), + (0, "A"), + (0, "D"), + (0, "F"), + (0, "E"), + (0, "H"), + (1, "G"), + (1, "A"), + (0, "I"), + (0, "B"), + (1, "H"), + ] + ) + G.add_edge((1, "C"), (0, "A")) + G.add_edge((1, "B"), (0, "A")) + G.add_edge((0, "G"), (1, "I")) + G.add_edge((0, "G"), (1, "H")) + G.add_edge((1, "F"), (0, "A")) + G.add_edge((1, "F"), (0, "C")) + G.add_edge((1, "F"), (0, "E")) + G.add_edge((1, "E"), (0, "A")) + G.add_edge((1, "E"), (0, "C")) + G.add_edge((0, "C"), (1, "D")) + G.add_edge((0, "C"), (1, "I")) + G.add_edge((0, "C"), (1, "G")) + G.add_edge((0, "C"), (1, "H")) + G.add_edge((1, "D"), (0, "A")) + G.add_edge((1, "I"), (0, "A")) + G.add_edge((1, "I"), (0, "E")) + G.add_edge((0, "A"), (1, "G")) + G.add_edge((0, "A"), (1, "H")) + G.add_edge((0, "E"), (1, "G")) + G.add_edge((0, "E"), (1, "H")) + self.disconnected_graph = G + + def check_match(self, matching): + """Asserts that the matching is what we expect from the bipartite graph + constructed in the :meth:`setup` fixture. + + """ + # For the sake of brevity, rename `matching` to `M`. + M = matching + matched_vertices = frozenset(itertools.chain(*M.items())) + # Assert that the maximum number of vertices (10) is matched. + assert matched_vertices == frozenset(range(12)) - {1, 10} + # Assert that no vertex appears in two edges, or in other words, that + # the matching (u, v) and (v, u) both appear in the matching + # dictionary. + assert all(u == M[M[u]] for u in range(12) if u in M) + + def check_vertex_cover(self, vertices): + """Asserts that the given set of vertices is the vertex cover we + expected from the bipartite graph constructed in the :meth:`setup` + fixture. + + """ + # By Konig's theorem, the number of edges in a maximum matching equals + # the number of vertices in a minimum vertex cover. + assert len(vertices) == 5 + # Assert that the set is truly a vertex cover. + for u, v in self.graph.edges(): + assert u in vertices or v in vertices + # TODO Assert that the vertices are the correct ones. + + def test_eppstein_matching(self): + """Tests that David Eppstein's implementation of the Hopcroft--Karp + algorithm produces a maximum cardinality matching. + + """ + self.check_match(eppstein_matching(self.graph, self.top_nodes)) + + def test_hopcroft_karp_matching(self): + """Tests that the Hopcroft--Karp algorithm produces a maximum + cardinality matching in a bipartite graph. + + """ + self.check_match(hopcroft_karp_matching(self.graph, self.top_nodes)) + + def test_to_vertex_cover(self): + """Test for converting a maximum matching to a minimum vertex cover.""" + matching = maximum_matching(self.graph, self.top_nodes) + vertex_cover = to_vertex_cover(self.graph, matching, self.top_nodes) + self.check_vertex_cover(vertex_cover) + + def test_eppstein_matching_simple(self): + match = eppstein_matching(self.simple_graph) + assert match == self.simple_solution + + def test_hopcroft_karp_matching_simple(self): + match = hopcroft_karp_matching(self.simple_graph) + assert match == self.simple_solution + + def test_eppstein_matching_disconnected(self): + with pytest.raises(nx.AmbiguousSolution): + match = eppstein_matching(self.disconnected_graph) + + def test_hopcroft_karp_matching_disconnected(self): + with pytest.raises(nx.AmbiguousSolution): + match = hopcroft_karp_matching(self.disconnected_graph) + + def test_issue_2127(self): + """Test from issue 2127""" + # Build the example DAG + G = nx.DiGraph() + G.add_edge("A", "C") + G.add_edge("A", "B") + G.add_edge("C", "E") + G.add_edge("C", "D") + G.add_edge("E", "G") + G.add_edge("E", "F") + G.add_edge("G", "I") + G.add_edge("G", "H") + + tc = nx.transitive_closure(G) + btc = nx.Graph() + + # Create a bipartite graph based on the transitive closure of G + for v in tc.nodes(): + btc.add_node((0, v)) + btc.add_node((1, v)) + + for u, v in tc.edges(): + btc.add_edge((0, u), (1, v)) + + top_nodes = {n for n in btc if n[0] == 0} + matching = hopcroft_karp_matching(btc, top_nodes) + vertex_cover = to_vertex_cover(btc, matching, top_nodes) + independent_set = set(G) - {v for _, v in vertex_cover} + assert {"B", "D", "F", "I", "H"} == independent_set + + def test_vertex_cover_issue_2384(self): + G = nx.Graph([(0, 3), (1, 3), (1, 4), (2, 3)]) + matching = maximum_matching(G) + vertex_cover = to_vertex_cover(G, matching) + for u, v in G.edges(): + assert u in vertex_cover or v in vertex_cover + + def test_vertex_cover_issue_3306(self): + G = nx.Graph() + edges = [(0, 2), (1, 0), (1, 1), (1, 2), (2, 2)] + G.add_edges_from([((i, "L"), (j, "R")) for i, j in edges]) + + matching = maximum_matching(G) + vertex_cover = to_vertex_cover(G, matching) + for u, v in G.edges(): + assert u in vertex_cover or v in vertex_cover + + def test_unorderable_nodes(self): + a = object() + b = object() + c = object() + d = object() + e = object() + G = nx.Graph([(a, d), (b, d), (b, e), (c, d)]) + matching = maximum_matching(G) + vertex_cover = to_vertex_cover(G, matching) + for u, v in G.edges(): + assert u in vertex_cover or v in vertex_cover + + +def test_eppstein_matching(): + """Test in accordance to issue #1927""" + G = nx.Graph() + G.add_nodes_from(["a", 2, 3, 4], bipartite=0) + G.add_nodes_from([1, "b", "c"], bipartite=1) + G.add_edges_from([("a", 1), ("a", "b"), (2, "b"), (2, "c"), (3, "c"), (4, 1)]) + matching = eppstein_matching(G) + assert len(matching) == len(maximum_matching(G)) + assert all(x in set(matching.keys()) for x in set(matching.values())) + + +class TestMinimumWeightFullMatching: + @classmethod + def setup_class(cls): + pytest.importorskip("scipy") + + def test_minimum_weight_full_matching_incomplete_graph(self): + B = nx.Graph() + B.add_nodes_from([1, 2], bipartite=0) + B.add_nodes_from([3, 4], bipartite=1) + B.add_edge(1, 4, weight=100) + B.add_edge(2, 3, weight=100) + B.add_edge(2, 4, weight=50) + matching = minimum_weight_full_matching(B) + assert matching == {1: 4, 2: 3, 4: 1, 3: 2} + + def test_minimum_weight_full_matching_with_no_full_matching(self): + B = nx.Graph() + B.add_nodes_from([1, 2, 3], bipartite=0) + B.add_nodes_from([4, 5, 6], bipartite=1) + B.add_edge(1, 4, weight=100) + B.add_edge(2, 4, weight=100) + B.add_edge(3, 4, weight=50) + B.add_edge(3, 5, weight=50) + B.add_edge(3, 6, weight=50) + with pytest.raises(ValueError): + minimum_weight_full_matching(B) + + def test_minimum_weight_full_matching_square(self): + G = nx.complete_bipartite_graph(3, 3) + G.add_edge(0, 3, weight=400) + G.add_edge(0, 4, weight=150) + G.add_edge(0, 5, weight=400) + G.add_edge(1, 3, weight=400) + G.add_edge(1, 4, weight=450) + G.add_edge(1, 5, weight=600) + G.add_edge(2, 3, weight=300) + G.add_edge(2, 4, weight=225) + G.add_edge(2, 5, weight=300) + matching = minimum_weight_full_matching(G) + assert matching == {0: 4, 1: 3, 2: 5, 4: 0, 3: 1, 5: 2} + + def test_minimum_weight_full_matching_smaller_left(self): + G = nx.complete_bipartite_graph(3, 4) + G.add_edge(0, 3, weight=400) + G.add_edge(0, 4, weight=150) + G.add_edge(0, 5, weight=400) + G.add_edge(0, 6, weight=1) + G.add_edge(1, 3, weight=400) + G.add_edge(1, 4, weight=450) + G.add_edge(1, 5, weight=600) + G.add_edge(1, 6, weight=2) + G.add_edge(2, 3, weight=300) + G.add_edge(2, 4, weight=225) + G.add_edge(2, 5, weight=290) + G.add_edge(2, 6, weight=3) + matching = minimum_weight_full_matching(G) + assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1} + + def test_minimum_weight_full_matching_smaller_top_nodes_right(self): + G = nx.complete_bipartite_graph(3, 4) + G.add_edge(0, 3, weight=400) + G.add_edge(0, 4, weight=150) + G.add_edge(0, 5, weight=400) + G.add_edge(0, 6, weight=1) + G.add_edge(1, 3, weight=400) + G.add_edge(1, 4, weight=450) + G.add_edge(1, 5, weight=600) + G.add_edge(1, 6, weight=2) + G.add_edge(2, 3, weight=300) + G.add_edge(2, 4, weight=225) + G.add_edge(2, 5, weight=290) + G.add_edge(2, 6, weight=3) + matching = minimum_weight_full_matching(G, top_nodes=[3, 4, 5, 6]) + assert matching == {0: 4, 1: 6, 2: 5, 4: 0, 5: 2, 6: 1} + + def test_minimum_weight_full_matching_smaller_right(self): + G = nx.complete_bipartite_graph(4, 3) + G.add_edge(0, 4, weight=400) + G.add_edge(0, 5, weight=400) + G.add_edge(0, 6, weight=300) + G.add_edge(1, 4, weight=150) + G.add_edge(1, 5, weight=450) + G.add_edge(1, 6, weight=225) + G.add_edge(2, 4, weight=400) + G.add_edge(2, 5, weight=600) + G.add_edge(2, 6, weight=290) + G.add_edge(3, 4, weight=1) + G.add_edge(3, 5, weight=2) + G.add_edge(3, 6, weight=3) + matching = minimum_weight_full_matching(G) + assert matching == {1: 4, 2: 6, 3: 5, 4: 1, 5: 3, 6: 2} + + def test_minimum_weight_full_matching_negative_weights(self): + G = nx.complete_bipartite_graph(2, 2) + G.add_edge(0, 2, weight=-2) + G.add_edge(0, 3, weight=0.2) + G.add_edge(1, 2, weight=-2) + G.add_edge(1, 3, weight=0.3) + matching = minimum_weight_full_matching(G) + assert matching == {0: 3, 1: 2, 2: 1, 3: 0} + + def test_minimum_weight_full_matching_different_weight_key(self): + G = nx.complete_bipartite_graph(2, 2) + G.add_edge(0, 2, mass=2) + G.add_edge(0, 3, mass=0.2) + G.add_edge(1, 2, mass=1) + G.add_edge(1, 3, mass=2) + matching = minimum_weight_full_matching(G, weight="mass") + assert matching == {0: 3, 1: 2, 2: 1, 3: 0} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..0d8aa1a881edb7ffa20ca5f4450e69a904e6d9ce --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_matrix.py @@ -0,0 +1,138 @@ +import itertools + +import pytest + +import networkx as nx +from networkx.algorithms import bipartite +from networkx.utils import edges_equal + +np = pytest.importorskip("numpy") +sp = pytest.importorskip("scipy") + + +class TestBiadjacencyMatrix: + def test_biadjacency_matrix_weight(self): + G = nx.path_graph(5) + G.add_edge(0, 1, weight=2, other=4) + X = [1, 3] + Y = [0, 2, 4] + M = bipartite.biadjacency_matrix(G, X, weight="weight") + assert M[0, 0] == 2 + M = bipartite.biadjacency_matrix(G, X, weight="other") + assert M[0, 0] == 4 + + def test_biadjacency_matrix(self): + tops = [2, 5, 10] + bots = [5, 10, 15] + for i in range(len(tops)): + G = bipartite.random_graph(tops[i], bots[i], 0.2) + top = [n for n, d in G.nodes(data=True) if d["bipartite"] == 0] + M = bipartite.biadjacency_matrix(G, top) + assert M.shape[0] == tops[i] + assert M.shape[1] == bots[i] + + def test_biadjacency_matrix_order(self): + G = nx.path_graph(5) + G.add_edge(0, 1, weight=2) + X = [3, 1] + Y = [4, 2, 0] + M = bipartite.biadjacency_matrix(G, X, Y, weight="weight") + assert M[1, 2] == 2 + + def test_biadjacency_matrix_empty_graph(self): + G = nx.empty_graph(2) + M = nx.bipartite.biadjacency_matrix(G, [0]) + assert np.array_equal(M.toarray(), np.array([[0]])) + + def test_null_graph(self): + with pytest.raises(nx.NetworkXError): + bipartite.biadjacency_matrix(nx.Graph(), []) + + def test_empty_graph(self): + with pytest.raises(nx.NetworkXError): + bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), []) + + def test_duplicate_row(self): + with pytest.raises(nx.NetworkXError): + bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [1, 1]) + + def test_duplicate_col(self): + with pytest.raises(nx.NetworkXError): + bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], [1, 1]) + + def test_format_keyword(self): + with pytest.raises(nx.NetworkXError): + bipartite.biadjacency_matrix(nx.Graph([(1, 0)]), [0], format="foo") + + def test_from_biadjacency_roundtrip(self): + B1 = nx.path_graph(5) + M = bipartite.biadjacency_matrix(B1, [0, 2, 4]) + B2 = bipartite.from_biadjacency_matrix(M) + assert nx.is_isomorphic(B1, B2) + + def test_from_biadjacency_weight(self): + M = sp.sparse.csc_array([[1, 2], [0, 3]]) + B = bipartite.from_biadjacency_matrix(M) + assert edges_equal(B.edges(), [(0, 2), (0, 3), (1, 3)]) + B = bipartite.from_biadjacency_matrix(M, edge_attribute="weight") + e = [(0, 2, {"weight": 1}), (0, 3, {"weight": 2}), (1, 3, {"weight": 3})] + assert edges_equal(B.edges(data=True), e) + + def test_from_biadjacency_multigraph(self): + M = sp.sparse.csc_array([[1, 2], [0, 3]]) + B = bipartite.from_biadjacency_matrix(M, create_using=nx.MultiGraph()) + assert edges_equal(B.edges(), [(0, 2), (0, 3), (0, 3), (1, 3), (1, 3), (1, 3)]) + + @pytest.mark.parametrize( + "row_order,column_order,create_using", + itertools.product( + (None, ("a", "b"), (25, (0, 5, 10))), + (None, ("c", "d"), (26, (0, 5, 10))), + (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph), + ), + ) + def test_from_biadjacency_nodelist(self, row_order, column_order, create_using): + M = sp.sparse.csc_array([[1, 2], [0, 3]]) + B_default = bipartite.from_biadjacency_matrix(M, create_using=create_using()) + B = bipartite.from_biadjacency_matrix( + M, + create_using=create_using(), + row_order=row_order, + column_order=column_order, + ) + + row_order = row_order if row_order else list(range(M.shape[0])) + column_order = ( + column_order + if column_order + else list(range(M.shape[0], M.shape[0] + M.shape[1])) + ) + + top_map = dict(enumerate(row_order)) + + bottom_map = {idx + M.shape[0]: node for idx, node in enumerate(column_order)} + + def map_edges(edges): + return [(top_map[u], bottom_map[v]) for u, v in edges] + + mapped_edges = map_edges(B_default.edges()) + assert edges_equal(mapped_edges, B.edges()) + + def test_invalid_from_biadjacency_nodelist(self): + M = sp.sparse.csc_array([[1, 2], [0, 3]]) + # For when top nodelist has the wrong length + row_order_invalid = ["a", "b", "c"] + # For when bottom nodelist has the wrong length + column_order_invalid = ["c", "d", "e"] + with pytest.raises(ValueError): + bipartite.from_biadjacency_matrix( + M, + create_using=nx.MultiGraph(), + row_order=row_order_invalid, + ) + with pytest.raises(ValueError): + bipartite.from_biadjacency_matrix( + M, + create_using=nx.MultiGraph(), + column_order=column_order_invalid, + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py new file mode 100644 index 0000000000000000000000000000000000000000..bf1c5cbeb69e84ae1f720446ec60e7d2f3aae8c9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_project.py @@ -0,0 +1,409 @@ +import pytest + +import networkx as nx +from networkx.algorithms import bipartite +from networkx.utils import edges_equal, nodes_equal + + +class TestBipartiteProject: + def test_path_projected_graph(self): + G = nx.path_graph(4) + P = bipartite.projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)]) + P = bipartite.projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)]) + G = nx.MultiGraph([(0, 1)]) + with pytest.raises(nx.NetworkXError, match="not defined for multigraphs"): + bipartite.projected_graph(G, [0]) + + def test_path_projected_properties_graph(self): + G = nx.path_graph(4) + G.add_node(1, name="one") + G.add_node(2, name="two") + P = bipartite.projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)]) + assert P.nodes[1]["name"] == G.nodes[1]["name"] + P = bipartite.projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)]) + assert P.nodes[2]["name"] == G.nodes[2]["name"] + + def test_path_collaboration_projected_graph(self): + G = nx.path_graph(4) + P = bipartite.collaboration_weighted_projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)]) + P[1][3]["weight"] = 1 + P = bipartite.collaboration_weighted_projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)]) + P[0][2]["weight"] = 1 + + def test_directed_path_collaboration_projected_graph(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + P = bipartite.collaboration_weighted_projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)]) + P[1][3]["weight"] = 1 + P = bipartite.collaboration_weighted_projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)]) + P[0][2]["weight"] = 1 + + def test_path_weighted_projected_graph(self): + G = nx.path_graph(4) + + with pytest.raises(nx.NetworkXAlgorithmError): + bipartite.weighted_projected_graph(G, [1, 2, 3, 3]) + + P = bipartite.weighted_projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)]) + P[1][3]["weight"] = 1 + P = bipartite.weighted_projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)]) + P[0][2]["weight"] = 1 + + def test_digraph_weighted_projection(self): + G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)]) + P = bipartite.overlap_weighted_projected_graph(G, [1, 3]) + assert nx.get_edge_attributes(P, "weight") == {(1, 3): 1.0} + assert len(P) == 2 + + def test_path_weighted_projected_directed_graph(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + P = bipartite.weighted_projected_graph(G, [1, 3]) + assert nodes_equal(list(P), [1, 3]) + assert edges_equal(list(P.edges()), [(1, 3)], directed=True) + P[1][3]["weight"] = 1 + P = bipartite.weighted_projected_graph(G, [0, 2]) + assert nodes_equal(list(P), [0, 2]) + assert edges_equal(list(P.edges()), [(0, 2)], directed=True) + P[0][2]["weight"] = 1 + + def test_star_projected_graph(self): + G = nx.star_graph(3) + P = bipartite.projected_graph(G, [1, 2, 3]) + assert nodes_equal(list(P), [1, 2, 3]) + assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)]) + P = bipartite.weighted_projected_graph(G, [1, 2, 3]) + assert nodes_equal(list(P), [1, 2, 3]) + assert edges_equal(list(P.edges()), [(1, 2), (1, 3), (2, 3)]) + + P = bipartite.projected_graph(G, [0]) + assert nodes_equal(list(P), [0]) + assert edges_equal(list(P.edges()), []) + + def test_project_multigraph(self): + G = nx.Graph() + G.add_edge("a", 1) + G.add_edge("b", 1) + G.add_edge("a", 2) + G.add_edge("b", 2) + P = bipartite.projected_graph(G, "ab") + assert edges_equal(list(P.edges()), [("a", "b")]) + P = bipartite.weighted_projected_graph(G, "ab") + assert edges_equal(list(P.edges()), [("a", "b")]) + P = bipartite.projected_graph(G, "ab", multigraph=True) + assert edges_equal(list(P.edges()), [("a", "b"), ("a", "b")]) + + def test_project_collaboration(self): + G = nx.Graph() + G.add_edge("a", 1) + G.add_edge("b", 1) + G.add_edge("b", 2) + G.add_edge("c", 2) + G.add_edge("c", 3) + G.add_edge("c", 4) + G.add_edge("b", 4) + P = bipartite.collaboration_weighted_projected_graph(G, "abc") + assert P["a"]["b"]["weight"] == 1 + assert P["b"]["c"]["weight"] == 2 + + def test_directed_projection(self): + G = nx.DiGraph() + G.add_edge("A", 1) + G.add_edge(1, "B") + G.add_edge("A", 2) + G.add_edge("B", 2) + P = bipartite.projected_graph(G, "AB") + assert edges_equal(list(P.edges()), [("A", "B")], directed=True) + P = bipartite.weighted_projected_graph(G, "AB") + assert edges_equal(list(P.edges()), [("A", "B")], directed=True) + assert P["A"]["B"]["weight"] == 1 + + P = bipartite.projected_graph(G, "AB", multigraph=True) + assert edges_equal(list(P.edges()), [("A", "B")], directed=True) + + G = nx.DiGraph() + G.add_edge("A", 1) + G.add_edge(1, "B") + G.add_edge("A", 2) + G.add_edge(2, "B") + P = bipartite.projected_graph(G, "AB") + assert edges_equal(list(P.edges()), [("A", "B")], directed=True) + P = bipartite.weighted_projected_graph(G, "AB") + assert edges_equal(list(P.edges()), [("A", "B")], directed=True) + assert P["A"]["B"]["weight"] == 2 + + P = bipartite.projected_graph(G, "AB", multigraph=True) + assert edges_equal(list(P.edges()), [("A", "B"), ("A", "B")], directed=True) + + +class TestBipartiteWeightedProjection: + @classmethod + def setup_class(cls): + # Tore Opsahl's example + # http://toreopsahl.com/2009/05/01/projecting-two-mode-networks-onto-weighted-one-mode-networks/ + cls.G = nx.Graph() + cls.G.add_edge("A", 1) + cls.G.add_edge("A", 2) + cls.G.add_edge("B", 1) + cls.G.add_edge("B", 2) + cls.G.add_edge("B", 3) + cls.G.add_edge("B", 4) + cls.G.add_edge("B", 5) + cls.G.add_edge("C", 1) + cls.G.add_edge("D", 3) + cls.G.add_edge("E", 4) + cls.G.add_edge("E", 5) + cls.G.add_edge("E", 6) + cls.G.add_edge("F", 6) + # Graph based on figure 6 from Newman (2001) + cls.N = nx.Graph() + cls.N.add_edge("A", 1) + cls.N.add_edge("A", 2) + cls.N.add_edge("A", 3) + cls.N.add_edge("B", 1) + cls.N.add_edge("B", 2) + cls.N.add_edge("B", 3) + cls.N.add_edge("C", 1) + cls.N.add_edge("D", 1) + cls.N.add_edge("E", 3) + + def test_project_weighted_shared(self): + edges = [ + ("A", "B", 2), + ("A", "C", 1), + ("B", "C", 1), + ("B", "D", 1), + ("B", "E", 2), + ("E", "F", 1), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.weighted_projected_graph(self.G, "ABCDEF") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + edges = [ + ("A", "B", 3), + ("A", "E", 1), + ("A", "C", 1), + ("A", "D", 1), + ("B", "E", 1), + ("B", "C", 1), + ("B", "D", 1), + ("C", "D", 1), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.weighted_projected_graph(self.N, "ABCDE") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + def test_project_weighted_newman(self): + edges = [ + ("A", "B", 1.5), + ("A", "C", 0.5), + ("B", "C", 0.5), + ("B", "D", 1), + ("B", "E", 2), + ("E", "F", 1), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.collaboration_weighted_projected_graph(self.G, "ABCDEF") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + edges = [ + ("A", "B", 11 / 6.0), + ("A", "E", 1 / 2.0), + ("A", "C", 1 / 3.0), + ("A", "D", 1 / 3.0), + ("B", "E", 1 / 2.0), + ("B", "C", 1 / 3.0), + ("B", "D", 1 / 3.0), + ("C", "D", 1 / 3.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.collaboration_weighted_projected_graph(self.N, "ABCDE") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + def test_project_weighted_ratio(self): + edges = [ + ("A", "B", 2 / 6.0), + ("A", "C", 1 / 6.0), + ("B", "C", 1 / 6.0), + ("B", "D", 1 / 6.0), + ("B", "E", 2 / 6.0), + ("E", "F", 1 / 6.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.weighted_projected_graph(self.G, "ABCDEF", ratio=True) + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + edges = [ + ("A", "B", 3 / 3.0), + ("A", "E", 1 / 3.0), + ("A", "C", 1 / 3.0), + ("A", "D", 1 / 3.0), + ("B", "E", 1 / 3.0), + ("B", "C", 1 / 3.0), + ("B", "D", 1 / 3.0), + ("C", "D", 1 / 3.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.weighted_projected_graph(self.N, "ABCDE", ratio=True) + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + def test_project_weighted_overlap(self): + edges = [ + ("A", "B", 2 / 2.0), + ("A", "C", 1 / 1.0), + ("B", "C", 1 / 1.0), + ("B", "D", 1 / 1.0), + ("B", "E", 2 / 3.0), + ("E", "F", 1 / 1.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF", jaccard=False) + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + edges = [ + ("A", "B", 3 / 3.0), + ("A", "E", 1 / 1.0), + ("A", "C", 1 / 1.0), + ("A", "D", 1 / 1.0), + ("B", "E", 1 / 1.0), + ("B", "C", 1 / 1.0), + ("B", "D", 1 / 1.0), + ("C", "D", 1 / 1.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE", jaccard=False) + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + def test_project_weighted_jaccard(self): + edges = [ + ("A", "B", 2 / 5.0), + ("A", "C", 1 / 2.0), + ("B", "C", 1 / 5.0), + ("B", "D", 1 / 5.0), + ("B", "E", 2 / 6.0), + ("E", "F", 1 / 3.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.overlap_weighted_projected_graph(self.G, "ABCDEF") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in list(P.edges()): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + edges = [ + ("A", "B", 3 / 3.0), + ("A", "E", 1 / 3.0), + ("A", "C", 1 / 3.0), + ("A", "D", 1 / 3.0), + ("B", "E", 1 / 3.0), + ("B", "C", 1 / 3.0), + ("B", "D", 1 / 3.0), + ("C", "D", 1 / 1.0), + ] + Panswer = nx.Graph() + Panswer.add_weighted_edges_from(edges) + P = bipartite.overlap_weighted_projected_graph(self.N, "ABCDE") + assert edges_equal(list(P.edges()), Panswer.edges()) + for u, v in P.edges(): + assert P[u][v]["weight"] == Panswer[u][v]["weight"] + + def test_generic_weighted_projected_graph_simple(self): + def shared(G, u, v): + return len(set(G[u]) & set(G[v])) + + B = nx.path_graph(5) + G = bipartite.generic_weighted_projected_graph( + B, [0, 2, 4], weight_function=shared + ) + assert nodes_equal(list(G), [0, 2, 4]) + assert edges_equal( + list(G.edges(data=True)), + [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})], + ) + + G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4]) + assert nodes_equal(list(G), [0, 2, 4]) + assert edges_equal( + list(G.edges(data=True)), + [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})], + ) + B = nx.DiGraph() + nx.add_path(B, range(5)) + G = bipartite.generic_weighted_projected_graph(B, [0, 2, 4]) + assert nodes_equal(list(G), [0, 2, 4]) + assert edges_equal( + list(G.edges(data=True)), + [(0, 2, {"weight": 1}), (2, 4, {"weight": 1})], + directed=True, + ) + + def test_generic_weighted_projected_graph_custom(self): + def jaccard(G, u, v): + unbrs = set(G[u]) + vnbrs = set(G[v]) + return len(unbrs & vnbrs) / len(unbrs | vnbrs) + + def my_weight(G, u, v, weight="weight"): + w = 0 + for nbr in set(G[u]) & set(G[v]): + w += G.edges[u, nbr].get(weight, 1) + G.edges[v, nbr].get(weight, 1) + return w + + B = nx.bipartite.complete_bipartite_graph(2, 2) + for i, (u, v) in enumerate(B.edges()): + B.edges[u, v]["weight"] = i + 1 + G = bipartite.generic_weighted_projected_graph( + B, [0, 1], weight_function=jaccard + ) + assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 1.0})]) + G = bipartite.generic_weighted_projected_graph( + B, [0, 1], weight_function=my_weight + ) + assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 10})]) + G = bipartite.generic_weighted_projected_graph(B, [0, 1]) + assert edges_equal(list(G.edges(data=True)), [(0, 1, {"weight": 2})]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py new file mode 100644 index 0000000000000000000000000000000000000000..8d979db8d8599b1ec59c2123d258bc29efaa4c9b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_redundancy.py @@ -0,0 +1,35 @@ +"""Unit tests for the :mod:`networkx.algorithms.bipartite.redundancy` module.""" + +import pytest + +from networkx import NetworkXError, cycle_graph +from networkx.algorithms.bipartite import complete_bipartite_graph, node_redundancy + + +def test_no_redundant_nodes(): + G = complete_bipartite_graph(2, 2) + + # when nodes is None + rc = node_redundancy(G) + assert all(redundancy == 1 for redundancy in rc.values()) + + # when set of nodes is specified + rc = node_redundancy(G, (2, 3)) + assert rc == {2: 1.0, 3: 1.0} + + +def test_redundant_nodes(): + G = cycle_graph(6) + edge = {0, 3} + G.add_edge(*edge) + redundancy = node_redundancy(G) + for v in edge: + assert redundancy[v] == 2 / 3 + for v in set(G) - edge: + assert redundancy[v] == 1 + + +def test_not_enough_neighbors(): + with pytest.raises(NetworkXError): + G = complete_bipartite_graph(1, 2) + node_redundancy(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py new file mode 100644 index 0000000000000000000000000000000000000000..b940649793d40aa73606914f3d48348761c329df --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py @@ -0,0 +1,80 @@ +import pytest + +pytest.importorskip("scipy") + +import networkx as nx +from networkx.algorithms.bipartite import spectral_bipartivity as sb + +# Examples from Figure 1 +# E. Estrada and J. A. Rodríguez-Velázquez, "Spectral measures of +# bipartivity in complex networks", PhysRev E 72, 046105 (2005) + + +class TestSpectralBipartivity: + def test_star_like(self): + # star-like + + G = nx.star_graph(2) + G.add_edge(1, 2) + assert sb(G) == pytest.approx(0.843, abs=1e-3) + + G = nx.star_graph(3) + G.add_edge(1, 2) + assert sb(G) == pytest.approx(0.871, abs=1e-3) + + G = nx.star_graph(4) + G.add_edge(1, 2) + assert sb(G) == pytest.approx(0.890, abs=1e-3) + + def test_k23_like(self): + # K2,3-like + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(0, 1) + assert sb(G) == pytest.approx(0.769, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + assert sb(G) == pytest.approx(0.829, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + G.add_edge(3, 4) + assert sb(G) == pytest.approx(0.731, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(0, 1) + G.add_edge(2, 4) + assert sb(G) == pytest.approx(0.692, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + G.add_edge(3, 4) + G.add_edge(0, 1) + assert sb(G) == pytest.approx(0.645, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + G.add_edge(3, 4) + G.add_edge(2, 3) + assert sb(G) == pytest.approx(0.645, abs=1e-3) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + G.add_edge(3, 4) + G.add_edge(2, 3) + G.add_edge(0, 1) + assert sb(G) == pytest.approx(0.597, abs=1e-3) + + def test_single_nodes(self): + # single nodes + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(2, 4) + sbn = sb(G, nodes=[1, 2]) + assert sbn[1] == pytest.approx(0.85, abs=1e-2) + assert sbn[2] == pytest.approx(0.77, abs=1e-2) + + G = nx.complete_bipartite_graph(2, 3) + G.add_edge(0, 1) + sbn = sb(G, nodes=[1, 2]) + assert sbn[1] == pytest.approx(0.73, abs=1e-2) + assert sbn[2] == pytest.approx(0.82, abs=1e-2) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c91a904a13496ecab5a3a6c8caa026970d99a540 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__init__.py @@ -0,0 +1,20 @@ +from .betweenness import * +from .betweenness_subset import * +from .closeness import * +from .current_flow_betweenness import * +from .current_flow_betweenness_subset import * +from .current_flow_closeness import * +from .degree_alg import * +from .dispersion import * +from .eigenvector import * +from .group import * +from .harmonic import * +from .katz import * +from .load import * +from .percolation import * +from .reaching import * +from .second_order import * +from .subgraph_alg import * +from .trophic import * +from .voterank_alg import * +from .laplacian import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc22d4343b9a16ee85b36ea43c32414a654d0c12 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8119b9730572a3ad25c59488ca0f2fd8d67a52ad Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness_subset.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness_subset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e6cef5c9e0d9891e59d11fa209d1d07a9ae5b4f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/betweenness_subset.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/closeness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/closeness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..476b352949778e6ccbdce75b1abf446aa8bf6e78 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/closeness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e58ee58321ee3ee3beae4a9fe4195589c76ee68 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness_subset.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness_subset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4faff33fb10d3775d96dc87c527bbdc555f0cd74 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_betweenness_subset.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_closeness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_closeness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6666f3738496f37b6a7d4bcffeb13cc1e01ca140 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/current_flow_closeness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/degree_alg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/degree_alg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d1546234d1a1def48d3b59d7cebd0284e840100 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/degree_alg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/dispersion.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/dispersion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..114548715f9e06d74bbc9067d4605b602a2bc8cc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/dispersion.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/eigenvector.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/eigenvector.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8cc5defa5293a5b10236bb396eba18d128e9a92 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/eigenvector.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/flow_matrix.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/flow_matrix.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb093ff63dc570829849395c18a4c782f580e3e3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/flow_matrix.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/group.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/group.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8547cb263f5fda9a7be38808bb83d1b9d9362527 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/group.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/harmonic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/harmonic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..082ffb0e84915657a97e213003df4667dd7b2414 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/harmonic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/katz.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/katz.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac514696c291c2efc62fa2212cce34686594783e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/katz.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/laplacian.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/laplacian.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a5f558df8c733d66f3e1b1be2834fbb3eac8280 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/laplacian.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/load.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/load.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1db74c5f1c2ae03862d3b60f560373d20c96bd0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/load.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/percolation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/percolation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..86c61781166e06b9357bd46c7774e6c844e299d1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/percolation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/reaching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/reaching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1a84eb58a1a43e1d12a3d5c2a45654e9b9bfb370 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/reaching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/second_order.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/second_order.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa1c2cc006ea4ad0a02a76bb62e42e1df651df1e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/second_order.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/subgraph_alg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/subgraph_alg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4674f3ce1b9c8c0cee41e3dadefefee6aaf7bf2d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/subgraph_alg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/trophic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/trophic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b799f987033ad643d8b83db1c1ace069a5c317f6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/trophic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/voterank_alg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/voterank_alg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..935af505f20cd8fbed25b6f0f37633775c282856 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/__pycache__/voterank_alg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness.py new file mode 100644 index 0000000000000000000000000000000000000000..3c549cc8c3a80472ad987b01353833f08f410d95 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness.py @@ -0,0 +1,591 @@ +"""Betweenness centrality measures.""" + +import math +from collections import deque +from heapq import heappop, heappush +from itertools import count + +import networkx as nx +from networkx.algorithms.shortest_paths.weighted import _weight_function +from networkx.utils import py_random_state +from networkx.utils.decorators import not_implemented_for + +__all__ = ["betweenness_centrality", "edge_betweenness_centrality"] + + +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def betweenness_centrality( + G, k=None, normalized=True, weight=None, endpoints=False, seed=None +): + r"""Compute the shortest-path betweenness centrality for nodes. + + Betweenness centrality of a node $v$ is the sum of the + fraction of all-pairs shortest paths that pass through $v$. + + .. math:: + + c_B(v) = \sum_{s, t \in V} \frac{\sigma(s, t | v)}{\sigma(s, t)} + + where $V$ is the set of nodes, $\sigma(s, t)$ is the number of + shortest $(s, t)$-paths, and $\sigma(s, t | v)$ is the number of + those paths passing through some node $v$ other than $s$ and $t$. + If $s = t$, $\sigma(s, t) = 1$, and if $v \in \{s, t\}$, + $\sigma(s, t | v) = 0$ [2]_. + The denominator $\sigma(s, t)$ is a normalization factor that can be + turned off to get the raw path counts. + + Parameters + ---------- + G : graph + A NetworkX graph. + + k : int, optional (default=None) + If `k` is not `None`, use `k` sampled nodes as sources for the considered paths. + The resulting sampled counts are then inflated to approximate betweenness. + Higher values of `k` give better approximation. Must have ``k <= len(G)``. + + normalized : bool, optional (default=True) + If `True`, the betweenness values are rescaled by dividing by the number of + possible $(s, t)$-pairs in the graph. + + weight : None or string, optional (default=None) + If `None`, all edge weights are 1. + Otherwise holds the name of the edge attribute used as weight. + Weights are used to calculate weighted shortest paths, so they are + interpreted as distances. + + endpoints : bool, optional (default=False) + If `True`, include the endpoints $s$ and $t$ in the shortest path counts. + This is taken into account when rescaling the values. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + Note that this is only used if ``k is not None``. + + Returns + ------- + nodes : dict + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality_subset + edge_betweenness_centrality + load_centrality + + Notes + ----- + The algorithm is from Ulrik Brandes [1]_. + See [4]_ for the original first published version and [2]_ for details on + algorithms for variations and related metrics. + + For approximate betweenness calculations, set `k` to the number of sampled + nodes ("pivots") used as sources to estimate the betweenness values. + The formula then sums over $s$ is in these pivots, instead of over all nodes. + The resulting sum is then inflated to approximate the full sum. + For a discussion of how to choose `k` for efficiency, see [3]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + Directed graphs and undirected graphs count paths differently. + In directed graphs, each pair of source-target nodes is considered separately + in each direction, as the shortest paths can differ by direction. + However, in undirected graphs, each pair of nodes is considered only once, + as the shortest paths are symmetric. + This means the normalization factor to divide by is $N(N-1)$ for directed graphs + and $N(N-1)/2$ for undirected graphs, where $N = n$ (the number of nodes) + if endpoints are included and $N = n-1$ otherwise. + + This algorithm is not guaranteed to be correct if edge weights + are floating point numbers. As a workaround you can use integer + numbers by multiplying the relevant edge attributes by a convenient + constant factor (e.g. 100) and converting to integers. + + References + ---------- + .. [1] Ulrik Brandes: + A Faster Algorithm for Betweenness Centrality. + Journal of Mathematical Sociology 25(2):163--177, 2001. + https://doi.org/10.1080/0022250X.2001.9990249 + .. [2] Ulrik Brandes: + On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136--145, 2008. + https://doi.org/10.1016/j.socnet.2007.11.001 + .. [3] Ulrik Brandes and Christian Pich: + Centrality Estimation in Large Networks. + International Journal of Bifurcation and Chaos 17(7):2303--2318, 2007. + https://dx.doi.org/10.1142/S0218127407018403 + .. [4] Linton C. Freeman: + A set of measures of centrality based on betweenness. + Sociometry 40: 35--41, 1977 + https://doi.org/10.2307/3033543 + + Examples + -------- + Consider an undirected 3-path. Each pair of nodes has exactly one shortest + path between them. Since the graph is undirected, only ordered pairs are counted. + Of these (and when `endpoints` is `False`), none of the shortest paths pass + through 0 and 2, and only the shortest path between 0 and 2 passes through 1. + As such, the counts should be ``{0: 0, 1: 1, 2: 0}``. + + >>> G = nx.path_graph(3) + >>> nx.betweenness_centrality(G, normalized=False, endpoints=False) + {0: 0.0, 1: 1.0, 2: 0.0} + + If `endpoints` is `True`, we also need to count endpoints as being on the path: + $\sigma(s, t | s) = \sigma(s, t | t) = \sigma(s, t)$. + In our example, 0 is then part of two shortest paths (0 to 1 and 0 to 2); + similarly, 2 is part of two shortest paths (0 to 2 and 1 to 2). + 1 is part of all three shortest paths. This makes the new raw + counts ``{0: 2, 1: 3, 2: 2}``. + + >>> nx.betweenness_centrality(G, normalized=False, endpoints=True) + {0: 2.0, 1: 3.0, 2: 2.0} + + With normalization, the values are divided by the number of ordered $(s, t)$-pairs. + If we are not counting endpoints, there are $n - 1$ possible choices for $s$ + (all except the node we are computing betweenness centrality for), which in turn + leaves $n - 2$ possible choices for $t$ as $s \ne t$. + The total number of ordered pairs when `endpoints` is `False` is $(n - 1)(n - 2)/2 = 1$. + If `endpoints` is `True`, there are $n(n - 1)/2 = 3$ ordered $(s, t)$-pairs to divide by. + + >>> nx.betweenness_centrality(G, normalized=True, endpoints=False) + {0: 0.0, 1: 1.0, 2: 0.0} + >>> nx.betweenness_centrality(G, normalized=True, endpoints=True) + {0: 0.6666666666666666, 1: 1.0, 2: 0.6666666666666666} + + If the graph is directed instead, we now need to consider $(s, t)$-pairs + in both directions. Our example becomes a directed 3-path. + Without counting endpoints, we only have one path through 1 (0 to 2). + This means the raw counts are ``{0: 0, 1: 1, 2: 0}``. + + >>> DG = nx.path_graph(3, create_using=nx.DiGraph) + >>> nx.betweenness_centrality(DG, normalized=False, endpoints=False) + {0: 0.0, 1: 1.0, 2: 0.0} + + If we do include endpoints, the raw counts are ``{0: 2, 1: 3, 2: 2}``. + + >>> nx.betweenness_centrality(DG, normalized=False, endpoints=True) + {0: 2.0, 1: 3.0, 2: 2.0} + + If we want to normalize directed betweenness centrality, the raw counts + are normalized by the number of $(s, t)$-pairs. There are $n(n - 1)$ + possible paths with endpoints and $(n - 1)(n - 2)$ without endpoints. + In our example, that's 6 with endpoints and 2 without endpoints. + + >>> nx.betweenness_centrality(DG, normalized=True, endpoints=True) + {0: 0.3333333333333333, 1: 0.5, 2: 0.3333333333333333} + >>> nx.betweenness_centrality(DG, normalized=True, endpoints=False) + {0: 0.0, 1: 0.5, 2: 0.0} + + Computing the full betweenness centrality can be costly. + This function can also be used to compute approximate betweenness centrality + by setting `k`. This only determines the number of source nodes to sample; + all nodes are targets. + + For simplicity, we only consider the case where endpoints are included in the counts. + Since the partial sums only include `k` terms, instead of ``n``, + we multiply them by ``n / k``, to approximate the full sum. + As the sets of sources and targets are not the same anymore, + paths have to be counted in a directed way. We thus count each as half a path. + This ensures that the results approximate the standard betweenness for ``k == n``. + + For instance, in the undirected 3-path graph case, setting ``k = 2`` (with ``seed=42``) + selects nodes 0 and 2 as sources. + This means only shortest paths starting at these nodes are considered. + The raw counts with endpoints are ``{0: 3, 1: 4, 2: 3}``. Accounting for the partial sum + and applying the undirectedness half-path correction, we get + + >>> nx.betweenness_centrality(G, k=2, normalized=False, endpoints=True, seed=42) + {0: 2.25, 1: 3.0, 2: 2.25} + + When normalizing, we instead want to divide by the total number of $(s, t)$-pairs. + This is $k(n - 1)$ with endpoints. + + >>> nx.betweenness_centrality(G, k=2, normalized=True, endpoints=True, seed=42) + {0: 0.75, 1: 1.0, 2: 0.75} + """ + betweenness = dict.fromkeys(G, 0.0) # b[v]=0 for v in G + if k == len(G): + # This is done for performance; the result is the same regardless. + k = None + if k is None: + nodes = G + else: + nodes = seed.sample(list(G.nodes()), k) + for s in nodes: + # single source shortest paths + if weight is None: # use BFS + S, P, sigma, _ = _single_source_shortest_path_basic(G, s) + else: # use Dijkstra's algorithm + S, P, sigma, _ = _single_source_dijkstra_path_basic(G, s, weight) + # accumulation + if endpoints: + betweenness, _ = _accumulate_endpoints(betweenness, S, P, sigma, s) + else: + betweenness, _ = _accumulate_basic(betweenness, S, P, sigma, s) + # rescaling + betweenness = _rescale( + betweenness, + len(G), + normalized=normalized, + directed=G.is_directed(), + endpoints=endpoints, + sampled_nodes=None if k is None else nodes, + ) + return betweenness + + +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def edge_betweenness_centrality(G, k=None, normalized=True, weight=None, seed=None): + r"""Compute betweenness centrality for edges. + + Betweenness centrality of an edge $e$ is the sum of the + fraction of all-pairs shortest paths that pass through $e$. + + .. math:: + + c_B(e) = \sum_{s, t \in V} \frac{\sigma(s, t | e)}{\sigma(s, t)} + + where $V$ is the set of nodes, $\sigma(s, t)$ is the number of + shortest $(s, t)$-paths, and $\sigma(s, t | e)$ is the number of + those paths passing through edge $e$ [1]_. + The denominator $\sigma(s, t)$ is a normalization factor that can be + turned off to get the raw path counts. + + Parameters + ---------- + G : graph + A NetworkX graph. + + k : int, optional (default=None) + If `k` is not `None`, use `k` sampled nodes as sources for the considered paths. + The resulting sampled counts are then inflated to approximate betweenness. + Higher values of `k` give better approximation. Must have ``k <= len(G)``. + + normalized : bool, optional (default=True) + If `True`, the betweenness values are rescaled by dividing by the number of + possible $(s, t)$-pairs in the graph. + + weight : None or string, optional (default=None) + If `None`, all edge weights are 1. + Otherwise holds the name of the edge attribute used as weight. + Weights are used to calculate weighted shortest paths, so they are + interpreted as distances. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + Note that this is only used if ``k is not None``. + + Returns + ------- + edges : dict + Dictionary of edges with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality_subset + edge_load + + Notes + ----- + The algorithm is from Ulrik Brandes [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + References + ---------- + .. [1] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136--145, 2008. + https://doi.org/10.1016/j.socnet.2007.11.001 + + Examples + -------- + Consider an undirected 3-path. Each pair of nodes has exactly one shortest + path between them. Since the graph is undirected, only ordered pairs are counted. + Each edge has two shortest paths passing through it. + As such, the raw counts should be ``{(0, 1): 2, (1, 2): 2}``. + + >>> G = nx.path_graph(3) + >>> nx.edge_betweenness_centrality(G, normalized=False) + {(0, 1): 2.0, (1, 2): 2.0} + + With normalization, the values are divided by the number of ordered $(s, t)$-pairs, + which is $n(n-1)/2$. For the 3-path, this is $3(3-1)/2 = 3$. + + >>> nx.edge_betweenness_centrality(G, normalized=True) + {(0, 1): 0.6666666666666666, (1, 2): 0.6666666666666666} + + For a directed graph, all $(s, t)$-pairs are considered. The normalization factor + is $n(n-1)$ to reflect this. + + >>> DG = nx.path_graph(3, create_using=nx.DiGraph) + >>> nx.edge_betweenness_centrality(DG, normalized=False) + {(0, 1): 2.0, (1, 2): 2.0} + >>> nx.edge_betweenness_centrality(DG, normalized=True) + {(0, 1): 0.3333333333333333, (1, 2): 0.3333333333333333} + + Computing the full edge betweenness centrality can be costly. + This function can also be used to compute approximate edge betweenness centrality + by setting `k`. This determines the number of source nodes to sample. + + Since the partial sums only include `k` terms, instead of ``n``, + we multiply them by ``n / k``, to approximate the full sum. + As the sets of sources and targets are not the same anymore, + paths have to be counted in a directed way. We thus count each as half a path. + This ensures that the results approximate the standard betweenness for ``k == n``. + + For instance, in the undirected 3-path graph case, setting ``k = 2`` (with ``seed=42``) + selects nodes 0 and 2 as sources. + This means only shortest paths starting at these nodes are considered. + The raw counts are ``{(0, 1): 3, (1, 2): 3}``. Accounting for the partial sum + and applying the undirectedness half-path correction, we get + + >>> nx.edge_betweenness_centrality(G, k=2, normalized=False, seed=42) + {(0, 1): 2.25, (1, 2): 2.25} + + When normalizing, we instead want to divide by the total number of $(s, t)$-pairs. + This is $k(n-1)$, which is $4$ in our case. + + >>> nx.edge_betweenness_centrality(G, k=2, normalized=True, seed=42) + {(0, 1): 0.75, (1, 2): 0.75} + """ + betweenness = dict.fromkeys(G, 0.0) # b[v]=0 for v in G + # b[e]=0 for e in G.edges() + betweenness.update(dict.fromkeys(G.edges(), 0.0)) + if k is None: + nodes = G + else: + nodes = seed.sample(list(G.nodes()), k) + for s in nodes: + # single source shortest paths + if weight is None: # use BFS + S, P, sigma, _ = _single_source_shortest_path_basic(G, s) + else: # use Dijkstra's algorithm + S, P, sigma, _ = _single_source_dijkstra_path_basic(G, s, weight) + # accumulation + betweenness = _accumulate_edges(betweenness, S, P, sigma, s) + # rescaling + for n in G: # remove nodes to only return edges + del betweenness[n] + betweenness = _rescale( + betweenness, + len(G), + normalized=normalized, + directed=G.is_directed(), + sampled_nodes=None if k is None else nodes, + ) + if G.is_multigraph(): + betweenness = _add_edge_keys(G, betweenness, weight=weight) + return betweenness + + +# helpers for betweenness centrality + + +def _single_source_shortest_path_basic(G, s): + S = [] + P = {} + for v in G: + P[v] = [] + sigma = dict.fromkeys(G, 0.0) # sigma[v]=0 for v in G + D = {} + sigma[s] = 1.0 + D[s] = 0 + Q = deque([s]) + while Q: # use BFS to find shortest paths + v = Q.popleft() + S.append(v) + Dv = D[v] + sigmav = sigma[v] + for w in G[v]: + if w not in D: + Q.append(w) + D[w] = Dv + 1 + if D[w] == Dv + 1: # this is a shortest path, count paths + sigma[w] += sigmav + P[w].append(v) # predecessors + return S, P, sigma, D + + +def _single_source_dijkstra_path_basic(G, s, weight): + weight = _weight_function(G, weight) + # modified from Eppstein + S = [] + P = {} + for v in G: + P[v] = [] + sigma = dict.fromkeys(G, 0.0) # sigma[v]=0 for v in G + D = {} + sigma[s] = 1.0 + seen = {s: 0} + c = count() + Q = [] # use Q as heap with (distance,node id) tuples + heappush(Q, (0, next(c), s, s)) + while Q: + (dist, _, pred, v) = heappop(Q) + if v in D: + continue # already searched this node. + sigma[v] += sigma[pred] # count paths + S.append(v) + D[v] = dist + for w, edgedata in G[v].items(): + vw_dist = dist + weight(v, w, edgedata) + if w not in D and (w not in seen or vw_dist < seen[w]): + seen[w] = vw_dist + heappush(Q, (vw_dist, next(c), v, w)) + sigma[w] = 0.0 + P[w] = [v] + elif vw_dist == seen[w]: # handle equal paths + sigma[w] += sigma[v] + P[w].append(v) + return S, P, sigma, D + + +def _accumulate_basic(betweenness, S, P, sigma, s): + delta = dict.fromkeys(S, 0) + while S: + w = S.pop() + coeff = (1 + delta[w]) / sigma[w] + for v in P[w]: + delta[v] += sigma[v] * coeff + if w != s: + betweenness[w] += delta[w] + return betweenness, delta + + +def _accumulate_endpoints(betweenness, S, P, sigma, s): + betweenness[s] += len(S) - 1 + delta = dict.fromkeys(S, 0) + while S: + w = S.pop() + coeff = (1 + delta[w]) / sigma[w] + for v in P[w]: + delta[v] += sigma[v] * coeff + if w != s: + betweenness[w] += delta[w] + 1 + return betweenness, delta + + +def _accumulate_edges(betweenness, S, P, sigma, s): + delta = dict.fromkeys(S, 0) + while S: + w = S.pop() + coeff = (1 + delta[w]) / sigma[w] + for v in P[w]: + c = sigma[v] * coeff + if (v, w) not in betweenness: + betweenness[(w, v)] += c + else: + betweenness[(v, w)] += c + delta[v] += c + if w != s: + betweenness[w] += delta[w] + return betweenness + + +def _rescale( + betweenness, n, *, normalized, directed, endpoints=True, sampled_nodes=None +): + # For edge betweenness, `endpoints` is always `True`. + + k = None if sampled_nodes is None else len(sampled_nodes) + # N is used to count the number of valid (s, t) pairs where s != t that + # could have a path pass through v. If endpoints is False, then v must + # not be the target t, hence why we subtract by 1. + N = n if endpoints else n - 1 + if N < 2: + # No rescaling necessary: b=0 for all nodes + return betweenness + + K_source = N if k is None else k + + if k is None or endpoints: + # No sampling adjustment needed + if normalized: + # Divide by the number of valid (s, t) node pairs that could have + # a path through v where s != t. + scale = 1 / (K_source * (N - 1)) + else: + # Scale to the full BC + if not directed: + # The non-normalized BC values are computed the same way for + # directed and undirected graphs: shortest paths are computed and + # counted for each *ordered* (s, t) pair. Undirected graphs should + # only count valid *unordered* node pairs {s, t}; that is, (s, t) + # and (t, s) should be counted only once. We correct for this here. + correction = 2 + else: + correction = 1 + scale = N / (K_source * correction) + + if scale != 1: + for v in betweenness: + betweenness[v] *= scale + return betweenness + + # Sampling adjustment needed when excluding endpoints when using k. In this + # case, we need to handle source nodes differently from non-source nodes, + # because source nodes can't include themselves since endpoints are excluded. + # Without this, k == n would be a special case that would violate the + # assumption that node `v` is not one of the (s, t) node pairs. + if normalized: + # NaN for undefined 0/0; there is no data for source node when k=1 + scale_source = 1 / ((K_source - 1) * (N - 1)) if K_source > 1 else math.nan + scale_nonsource = 1 / (K_source * (N - 1)) + else: + correction = 1 if directed else 2 + scale_source = N / ((K_source - 1) * correction) if K_source > 1 else math.nan + scale_nonsource = N / (K_source * correction) + + sampled_nodes = set(sampled_nodes) + for v in betweenness: + betweenness[v] *= scale_source if v in sampled_nodes else scale_nonsource + return betweenness + + +@not_implemented_for("graph") +def _add_edge_keys(G, betweenness, weight=None): + r"""Adds the corrected betweenness centrality (BC) values for multigraphs. + + Parameters + ---------- + G : NetworkX graph. + + betweenness : dictionary + Dictionary mapping adjacent node tuples to betweenness centrality values. + + weight : string or function + See `_weight_function` for details. Defaults to `None`. + + Returns + ------- + edges : dictionary + The parameter `betweenness` including edges with keys and their + betweenness centrality values. + + The BC value is divided among edges of equal weight. + """ + _weight = _weight_function(G, weight) + + edge_bc = dict.fromkeys(G.edges, 0.0) + for u, v in betweenness: + d = G[u][v] + wt = _weight(u, v, d) + keys = [k for k in d if _weight(u, v, {k: d[k]}) == wt] + bc = betweenness[(u, v)] / len(keys) + for k in keys: + edge_bc[(u, v, k)] = bc + + return edge_bc diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness_subset.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness_subset.py new file mode 100644 index 0000000000000000000000000000000000000000..94071b58c6a513e9a9e32f067c65307509271860 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/betweenness_subset.py @@ -0,0 +1,236 @@ +"""Betweenness centrality measures for subsets of nodes.""" + +import networkx as nx +from networkx.algorithms.centrality.betweenness import ( + _add_edge_keys, + _rescale, +) +from networkx.algorithms.centrality.betweenness import ( + _single_source_dijkstra_path_basic as dijkstra, +) +from networkx.algorithms.centrality.betweenness import ( + _single_source_shortest_path_basic as shortest_path, +) + +__all__ = [ + "betweenness_centrality_subset", + "edge_betweenness_centrality_subset", +] + + +@nx._dispatchable(edge_attrs="weight") +def betweenness_centrality_subset(G, sources, targets, normalized=False, weight=None): + r"""Compute betweenness centrality for a subset of nodes. + + .. math:: + + c_B(v) = \sum_{s \in S, t \in T} \frac{\sigma(s, t | v)}{\sigma(s, t)} + + where $S$ is the set of sources, $T$ is the set of targets, + $\sigma(s, t)$ is the number of shortest $(s, t)$-paths, + and $\sigma(s, t | v)$ is the number of those paths + passing through some node $v$ other than $s$ and $t$. + If $s = t$, $\sigma(s, t) = 1$, + and if $v \in \{s, t\}$, $\sigma(s, t | v) = 0$ [2]_. + The denominator $\sigma(s, t)$ is a normalization factor that can be + turned off to get the raw path counts. + + Parameters + ---------- + G : graph + A NetworkX graph. + + sources: list of nodes + Nodes to use as sources for shortest paths in betweenness. + + targets: list of nodes + Nodes to use as targets for shortest paths in betweenness. + + normalized : bool, optional (default=False) + If `True`, the betweenness values are rescaled by dividing by the number of + possible $(s, t)$-pairs in the graph. + + weight : None or string, optional (default=None) + If `None`, all edge weights are 1. + Otherwise holds the name of the edge attribute used as weight. + Weights are used to calculate weighted shortest paths, so they are + interpreted as distances. + + Returns + ------- + nodes : dict + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality + edge_betweenness_centrality_subset + load_centrality + + Notes + ----- + The basic algorithm is from [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The normalization might seem a little strange but it is + designed to make betweenness_centrality(G) be the same as + betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()). + + The total number of paths between source and target is counted + differently for directed and undirected graphs. Directed paths + are easy to count. Undirected paths are tricky: should a path + from ``u`` to ``v`` count as 1 undirected path or as 2 directed paths? + We are only counting the paths in one direction. They are + undirected paths but we are counting them in a directed way. + To count them as undirected paths, each should count as half a path. + + References + ---------- + .. [1] Ulrik Brandes, A Faster Algorithm for Betweenness Centrality. + Journal of Mathematical Sociology 25(2):163-177, 2001. + https://doi.org/10.1080/0022250X.2001.9990249 + .. [2] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + https://doi.org/10.1016/j.socnet.2007.11.001 + """ + b = dict.fromkeys(G, 0.0) # b[v]=0 for v in G + for s in sources: + # single source shortest paths + if weight is None: # use BFS + S, P, sigma, _ = shortest_path(G, s) + else: # use Dijkstra's algorithm + S, P, sigma, _ = dijkstra(G, s, weight) + b = _accumulate_subset(b, S, P, sigma, s, targets) + b = _rescale( + b, len(G), normalized=normalized, directed=G.is_directed(), endpoints=False + ) + return b + + +@nx._dispatchable(edge_attrs="weight") +def edge_betweenness_centrality_subset( + G, sources, targets, normalized=False, weight=None +): + r"""Compute betweenness centrality for edges for a subset of nodes. + + .. math:: + + c_B(e) = \sum_{s \in S, t \in T} \frac{\sigma(s, t | e)}{\sigma(s, t)} + + where $S$ is the set of sources, $T$ is the set of targets, + $\sigma(s, t)$ is the number of shortest $(s, t)$-paths, + and $\sigma(s, t | e)$ is the number of those paths + passing through edge $e$ [1]_. + The denominator $\sigma(s, t)$ is a normalization factor that can be + turned off to get the raw path counts. + + Parameters + ---------- + G : graph + A networkx graph. + + sources: list of nodes + Nodes to use as sources for shortest paths in betweenness. + + targets: list of nodes + Nodes to use as targets for shortest paths in betweenness. + + normalized : bool, optional (default=False) + If `True`, the betweenness values are rescaled by dividing by the number of + possible $(s, t)$-pairs in the graph. + + weight : None or string, optional (default=None) + If `None`, all edge weights are 1. + Otherwise holds the name of the edge attribute used as weight. + Weights are used to calculate weighted shortest paths, so they are + interpreted as distances. + + Returns + ------- + edges : dict + Dictionary of edges with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + betweenness_centrality_subset + edge_betweenness_centrality + edge_load + + Notes + ----- + The basic algorithm is from [1]_. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The normalization might seem a little strange but it is the same + as in edge_betweenness_centrality() and is designed to make + edge_betweenness_centrality(G) be the same as + edge_betweenness_centrality_subset(G,sources=G.nodes(),targets=G.nodes()). + + References + ---------- + .. [1] Ulrik Brandes: On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + https://doi.org/10.1016/j.socnet.2007.11.001 + """ + b = dict.fromkeys(G, 0.0) # b[v]=0 for v in G + b.update(dict.fromkeys(G.edges(), 0.0)) # b[e] for e in G.edges() + for s in sources: + # single source shortest paths + if weight is None: # use BFS + S, P, sigma, _ = shortest_path(G, s) + else: # use Dijkstra's algorithm + S, P, sigma, _ = dijkstra(G, s, weight) + b = _accumulate_edges_subset(b, S, P, sigma, s, targets) + for n in G: # remove nodes to only return edges + del b[n] + b = _rescale(b, len(G), normalized=normalized, directed=G.is_directed()) + if G.is_multigraph(): + b = _add_edge_keys(G, b, weight=weight) + return b + + +def _accumulate_subset(betweenness, S, P, sigma, s, targets): + delta = dict.fromkeys(S, 0.0) + target_set = set(targets) - {s} + while S: + w = S.pop() + if w in target_set: + coeff = (delta[w] + 1.0) / sigma[w] + else: + coeff = delta[w] / sigma[w] + for v in P[w]: + delta[v] += sigma[v] * coeff + if w != s: + betweenness[w] += delta[w] + return betweenness + + +def _accumulate_edges_subset(betweenness, S, P, sigma, s, targets): + """edge_betweenness_centrality_subset helper.""" + delta = dict.fromkeys(S, 0) + target_set = set(targets) + while S: + w = S.pop() + for v in P[w]: + if w in target_set: + c = (sigma[v] / sigma[w]) * (1.0 + delta[w]) + else: + c = delta[w] / len(P[w]) + if (v, w) not in betweenness: + betweenness[(w, v)] += c + else: + betweenness[(v, w)] += c + delta[v] += c + if w != s: + betweenness[w] += delta[w] + return betweenness diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/closeness.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/closeness.py new file mode 100644 index 0000000000000000000000000000000000000000..1cc2f9599f2a3af8fa653b8faef58a9a8f2d2355 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/closeness.py @@ -0,0 +1,282 @@ +""" +Closeness centrality measures. +""" + +import functools + +import networkx as nx +from networkx.exception import NetworkXError +from networkx.utils.decorators import not_implemented_for + +__all__ = ["closeness_centrality", "incremental_closeness_centrality"] + + +@nx._dispatchable(edge_attrs="distance") +def closeness_centrality(G, u=None, distance=None, wf_improved=True): + r"""Compute closeness centrality for nodes. + + Closeness centrality [1]_ of a node `u` is the reciprocal of the + average shortest path distance to `u` over all `n-1` reachable nodes. + + .. math:: + + C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where `d(v, u)` is the shortest-path distance between `v` and `u`, + and `n-1` is the number of nodes reachable from `u`. Notice that the + closeness distance function computes the incoming distance to `u` + for directed graphs. To use outward distance, act on `G.reverse()`. + + Notice that higher values of closeness indicate higher centrality. + + Wasserman and Faust propose an improved formula for graphs with + more than one connected component. The result is "a ratio of the + fraction of actors in the group who are reachable, to the average + distance" from the reachable actors [2]_. You might think this + scale factor is inverted but it is not. As is, nodes from small + components receive a smaller closeness value. Letting `N` denote + the number of nodes in the graph, + + .. math:: + + C_{WF}(u) = \frac{n-1}{N-1} \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + Parameters + ---------- + G : graph + A NetworkX graph + + u : node, optional + Return only the value for node u + + distance : edge attribute key, optional (default=None) + Use the specified edge attribute as the edge distance in shortest + path calculations. If `None` (the default) all edges have a distance of 1. + Absent edge attributes are assigned a distance of 1. Note that no check + is performed to ensure that edges have the provided attribute. + + wf_improved : bool, optional (default=True) + If True, scale by the fraction of nodes reachable. This gives the + Wasserman and Faust improved formula. For single component graphs + it is the same as the original formula. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with closeness centrality as the value. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)]) + >>> nx.closeness_centrality(G) + {0: 1.0, 1: 1.0, 2: 0.75, 3: 0.75} + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality, + degree_centrality, incremental_closeness_centrality + + Notes + ----- + The closeness centrality is normalized to `(n-1)/(|G|-1)` where + `n` is the number of nodes in the connected part of graph + containing the node. If the graph is not completely connected, + this algorithm computes the closeness centrality for each + connected part separately scaled by that parts size. + + If the 'distance' keyword is set to an edge attribute key then the + shortest-path length will be computed using Dijkstra's algorithm with + that edge attribute as the edge weight. + + The closeness centrality uses *inward* distance to a node, not outward. + If you want to use outword distances apply the function to `G.reverse()` + + In NetworkX 2.2 and earlier a bug caused Dijkstra's algorithm to use the + outward distance rather than the inward distance. If you use a 'distance' + keyword and a DiGraph, your results will change between v2.2 and v2.3. + + References + ---------- + .. [1] Linton C. Freeman: Centrality in networks: I. + Conceptual clarification. Social Networks 1:215-239, 1979. + https://doi.org/10.1016/0378-8733(78)90021-7 + .. [2] pg. 201 of Wasserman, S. and Faust, K., + Social Network Analysis: Methods and Applications, 1994, + Cambridge University Press. + """ + if G.is_directed(): + G = G.reverse() # create a reversed graph view + + if distance is not None: + # use Dijkstra's algorithm with specified attribute as edge weight + path_length = functools.partial( + nx.single_source_dijkstra_path_length, weight=distance + ) + else: + path_length = nx.single_source_shortest_path_length + + if u is None: + nodes = G.nodes + else: + nodes = [u] + closeness_dict = {} + for n in nodes: + sp = path_length(G, n) + totsp = sum(sp.values()) + len_G = len(G) + _closeness_centrality = 0.0 + if totsp > 0.0 and len_G > 1: + _closeness_centrality = (len(sp) - 1.0) / totsp + # normalize to number of nodes-1 in connected part + if wf_improved: + s = (len(sp) - 1.0) / (len_G - 1) + _closeness_centrality *= s + closeness_dict[n] = _closeness_centrality + if u is not None: + return closeness_dict[u] + return closeness_dict + + +@not_implemented_for("directed") +@nx._dispatchable(mutates_input=True) +def incremental_closeness_centrality( + G, edge, prev_cc=None, insertion=True, wf_improved=True +): + r"""Incremental closeness centrality for nodes. + + Compute closeness centrality for nodes using level-based work filtering + as described in Incremental Algorithms for Closeness Centrality by Sariyuce et al. + + Level-based work filtering detects unnecessary updates to the closeness + centrality and filters them out. + + --- + From "Incremental Algorithms for Closeness Centrality": + + Theorem 1: Let :math:`G = (V, E)` be a graph and u and v be two vertices in V + such that there is no edge (u, v) in E. Let :math:`G' = (V, E \cup uv)` + Then :math:`cc[s] = cc'[s]` if and only if :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`. + + Where :math:`dG(u, v)` denotes the length of the shortest path between + two vertices u, v in a graph G, cc[s] is the closeness centrality for a + vertex s in V, and cc'[s] is the closeness centrality for a + vertex s in V, with the (u, v) edge added. + --- + + We use Theorem 1 to filter out updates when adding or removing an edge. + When adding an edge (u, v), we compute the shortest path lengths from all + other nodes to u and to v before the node is added. When removing an edge, + we compute the shortest path lengths after the edge is removed. Then we + apply Theorem 1 to use previously computed closeness centrality for nodes + where :math:`\left|dG(s, u) - dG(s, v)\right| \leq 1`. This works only for + undirected, unweighted graphs; the distance argument is not supported. + + Closeness centrality [1]_ of a node `u` is the reciprocal of the + sum of the shortest path distances from `u` to all `n-1` other nodes. + Since the sum of distances depends on the number of nodes in the + graph, closeness is normalized by the sum of minimum possible + distances `n-1`. + + .. math:: + + C(u) = \frac{n - 1}{\sum_{v=1}^{n-1} d(v, u)}, + + where `d(v, u)` is the shortest-path distance between `v` and `u`, + and `n` is the number of nodes in the graph. + + Notice that higher values of closeness indicate higher centrality. + + Parameters + ---------- + G : graph + A NetworkX graph + + edge : tuple + The modified edge (u, v) in the graph. + + prev_cc : dictionary + The previous closeness centrality for all nodes in the graph. + + insertion : bool, optional + If True (default) the edge was inserted, otherwise it was deleted from the graph. + + wf_improved : bool, optional (default=True) + If True, scale by the fraction of nodes reachable. This gives the + Wasserman and Faust improved formula. For single component graphs + it is the same as the original formula. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with closeness centrality as the value. + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality, + degree_centrality, closeness_centrality + + Notes + ----- + The closeness centrality is normalized to `(n-1)/(|G|-1)` where + `n` is the number of nodes in the connected part of graph + containing the node. If the graph is not completely connected, + this algorithm computes the closeness centrality for each + connected part separately. + + References + ---------- + .. [1] Freeman, L.C., 1979. Centrality in networks: I. + Conceptual clarification. Social Networks 1, 215--239. + https://doi.org/10.1016/0378-8733(78)90021-7 + .. [2] Sariyuce, A.E. ; Kaya, K. ; Saule, E. ; Catalyiirek, U.V. Incremental + Algorithms for Closeness Centrality. 2013 IEEE International Conference on Big Data + http://sariyuce.com/papers/bigdata13.pdf + """ + if prev_cc is not None and set(prev_cc.keys()) != set(G.nodes()): + raise NetworkXError("prev_cc and G do not have the same nodes") + + # Unpack edge + (u, v) = edge + path_length = nx.single_source_shortest_path_length + + if insertion: + # For edge insertion, we want shortest paths before the edge is inserted + du = path_length(G, u) + dv = path_length(G, v) + + G.add_edge(u, v) + else: + G.remove_edge(u, v) + + # For edge removal, we want shortest paths after the edge is removed + du = path_length(G, u) + dv = path_length(G, v) + + if prev_cc is None: + return nx.closeness_centrality(G) + + nodes = G.nodes() + closeness_dict = {} + for n in nodes: + if n in du and n in dv and abs(du[n] - dv[n]) <= 1: + closeness_dict[n] = prev_cc[n] + else: + sp = path_length(G, n) + totsp = sum(sp.values()) + len_G = len(G) + _closeness_centrality = 0.0 + if totsp > 0.0 and len_G > 1: + _closeness_centrality = (len(sp) - 1.0) / totsp + # normalize to number of nodes-1 in connected part + if wf_improved: + s = (len(sp) - 1.0) / (len_G - 1) + _closeness_centrality *= s + closeness_dict[n] = _closeness_centrality + + # Leave the graph as we found it + if insertion: + G.remove_edge(u, v) + else: + G.add_edge(u, v) + + return closeness_dict diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness.py new file mode 100644 index 0000000000000000000000000000000000000000..ce2e211c5dd790f12a22bf4c80aec8e7016eb27f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness.py @@ -0,0 +1,364 @@ +"""Current-flow betweenness centrality measures.""" + +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import ( + CGInverseLaplacian, + FullInverseLaplacian, + SuperLUInverseLaplacian, + flow_matrix_row, +) +from networkx.utils import ( + not_implemented_for, + py_random_state, + reverse_cuthill_mckee_ordering, +) + +__all__ = [ + "current_flow_betweenness_centrality", + "approximate_current_flow_betweenness_centrality", + "edge_current_flow_betweenness_centrality", +] + + +@not_implemented_for("directed") +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def approximate_current_flow_betweenness_centrality( + G, + normalized=True, + weight=None, + dtype=float, + solver="full", + epsilon=0.5, + kmax=10000, + seed=None, + *, + sample_weight=1, +): + r"""Compute the approximate current-flow betweenness centrality for nodes. + + Approximates the current-flow betweenness centrality within absolute + error of epsilon with high probability [1]_. + + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default=None) + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + The weight reflects the capacity or the strength of the + edge. + + dtype : data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver : string (default='full') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + epsilon: float + Absolute error tolerance. Note that smaller values of `epsilon` lead to + higher numbers of sample pairs (``k``) and thus more computation time. The number + of sample pairs is approximately ``(c/epsilon)^2 * log(n)`` where ``n`` is the + number of nodes. + + kmax: int + Maximum number of sample node pairs to use for approximation. + + sample_weight : float (default=1) + Multiplicative factor for the number of sample node pairs used in approximation. + Higher values may improve accuracy at the expense of increased computation time. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + current_flow_betweenness_centrality + + Notes + ----- + The running time is $O((1/\epsilon^2)m{\sqrt k} \log n)$ + and the space required is $O(m)$ for $n$ nodes and $m$ edges. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Ulrik Brandes and Daniel Fleischer: + Centrality Measures Based on Current Flow. + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + """ + import numpy as np + + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + + n = G.number_of_nodes() + + # For small graphs (n < 3), betweenness centrality is always 0 for all nodes + # since no node can be "between" any pair of other nodes + if n < 3: + return dict.fromkeys(G, 0.0) + + if epsilon <= 0: + raise nx.NetworkXError(f"Epsilon must be positive. Got {epsilon=}.") + + if sample_weight <= 0: + raise nx.NetworkXError(f"Sample weight must be positive. Got {sample_weight=}.") + + nb = (n - 1.0) * (n - 2.0) # normalization factor + cstar = n * (n - 1) / nb + k = int(sample_weight * np.ceil((cstar / epsilon) ** 2 * np.log(n))) + if k > kmax: + msg = f"Number random pairs k>kmax ({k}>{kmax}) " + raise nx.NetworkXError(msg, "Increase kmax or epsilon") + + solvername = { + "full": FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian, + } + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G, dict(zip(ordering, range(n)))) + L = nx.laplacian_matrix(H, nodelist=range(n), weight=weight).asformat("csc") + L = L.astype(dtype) + C = solvername[solver](L, dtype=dtype) # initialize solver + betweenness = dict.fromkeys(H, 0.0) + cstar2k = cstar / (2 * k) + for _ in range(k): + s, t = pair = seed.sample(range(n), 2) + b = np.zeros(n, dtype=dtype) + b[s] = 1 + b[t] = -1 + p = C.solve(b) + for v in H: + if v in pair: + continue + for nbr in H[v]: + w = H[v][nbr].get(weight, 1.0) + betweenness[v] += float(w * np.abs(p[v] - p[nbr]) * cstar2k) + if normalized: + factor = 1.0 + else: + factor = nb / 2.0 + # remap to original node names and "unnormalize" if required + return {ordering[k]: v * factor for k, v in betweenness.items()} + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def current_flow_betweenness_centrality( + G, normalized=True, weight=None, dtype=float, solver="full" +): + r"""Compute current-flow betweenness centrality for nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default=None) + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + The weight reflects the capacity or the strength of the + edge. + + dtype : data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver : string (default='full') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + approximate_current_flow_betweenness_centrality + betweenness_centrality + edge_betweenness_centrality + edge_current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$ + time [1]_, where $I(n-1)$ is the time needed to compute the + inverse Laplacian. For a full matrix this is $O(n^3)$ but using + sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the + Laplacian matrix condition number. + + The space required is $O(nw)$ where $w$ is the width of the sparse + Laplacian matrix. Worse case is $w=n$ for $O(n^2)$. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + N = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G, dict(zip(ordering, range(N)))) + betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H + for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver): + pos = dict(zip(row.argsort()[::-1], range(N))) + for i in range(N): + betweenness[s] += (i - pos[i]) * row.item(i) + betweenness[t] += (N - i - 1 - pos[i]) * row.item(i) + if normalized: + nb = (N - 1.0) * (N - 2.0) # normalization factor + else: + nb = 2.0 + return {ordering[n]: (b - n) * 2.0 / nb for n, b in betweenness.items()} + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def edge_current_flow_betweenness_centrality( + G, normalized=True, weight=None, dtype=float, solver="full" +): + r"""Compute current-flow betweenness centrality for edges. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by 2/[(n-1)(n-2)] where + n is the number of nodes in G. + + weight : string or None, optional (default=None) + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + The weight reflects the capacity or the strength of the + edge. + + dtype : data type (default=float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver : string (default='full') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of edge tuples with betweenness centrality as the value. + + Raises + ------ + NetworkXError + The algorithm does not support DiGraphs. + If the input graph is an instance of DiGraph class, NetworkXError + is raised. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality + current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$ + time [1]_, where $I(n-1)$ is the time needed to compute the + inverse Laplacian. For a full matrix this is $O(n^3)$ but using + sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the + Laplacian matrix condition number. + + The space required is $O(nw)$ where $w$ is the width of the sparse + Laplacian matrix. Worse case is $w=n$ for $O(n^2)$. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + N = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G, dict(zip(ordering, range(N)))) + edges = (tuple(sorted((u, v))) for u, v in H.edges()) + betweenness = dict.fromkeys(edges, 0.0) + if normalized: + nb = (N - 1.0) * (N - 2.0) # normalization factor + else: + nb = 2.0 + for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver): + pos = dict(zip(row.argsort()[::-1], range(1, N + 1))) + for i in range(N): + betweenness[e] += (i + 1 - pos[i]) * row.item(i) + betweenness[e] += (N - i - pos[i]) * row.item(i) + betweenness[e] /= nb + return {(ordering[s], ordering[t]): b for (s, t), b in betweenness.items()} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness_subset.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness_subset.py new file mode 100644 index 0000000000000000000000000000000000000000..911718c80bd50589abe645e44e862add4fc8dbcd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_betweenness_subset.py @@ -0,0 +1,227 @@ +"""Current-flow betweenness centrality measures for subsets of nodes.""" + +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import flow_matrix_row +from networkx.utils import not_implemented_for, reverse_cuthill_mckee_ordering + +__all__ = [ + "current_flow_betweenness_centrality_subset", + "edge_current_flow_betweenness_centrality_subset", +] + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def current_flow_betweenness_centrality_subset( + G, sources, targets, normalized=True, weight=None, dtype=float, solver="lu" +): + r"""Compute current-flow betweenness centrality for subsets of nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + sources: list of nodes + Nodes to use as sources for current + + targets: list of nodes + Nodes to use as sinks for current + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : string or None, optional (default=None) + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + The weight reflects the capacity or the strength of the + edge. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with betweenness centrality as the value. + + See Also + -------- + approximate_current_flow_betweenness_centrality + betweenness_centrality + edge_betweenness_centrality + edge_current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$ + time [1]_, where $I(n-1)$ is the time needed to compute the + inverse Laplacian. For a full matrix this is $O(n^3)$ but using + sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the + Laplacian matrix condition number. + + The space required is $O(nw)$ where $w$ is the width of the sparse + Laplacian matrix. Worse case is $w=n$ for $O(n^2)$. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + import numpy as np + + from networkx.utils import reverse_cuthill_mckee_ordering + + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + N = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + mapping = dict(zip(ordering, range(N))) + H = nx.relabel_nodes(G, mapping) + betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H + for row, (s, t) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver): + for ss in sources: + i = mapping[ss] + for tt in targets: + j = mapping[tt] + betweenness[s] += 0.5 * abs(row.item(i) - row.item(j)) + betweenness[t] += 0.5 * abs(row.item(i) - row.item(j)) + if normalized: + nb = (N - 1.0) * (N - 2.0) # normalization factor + else: + nb = 2.0 + for node in H: + betweenness[node] = betweenness[node] / nb + 1.0 / (2 - N) + return {ordering[node]: value for node, value in betweenness.items()} + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def edge_current_flow_betweenness_centrality_subset( + G, sources, targets, normalized=True, weight=None, dtype=float, solver="lu" +): + r"""Compute current-flow betweenness centrality for edges using subsets + of nodes. + + Current-flow betweenness centrality uses an electrical current + model for information spreading in contrast to betweenness + centrality which uses shortest paths. + + Current-flow betweenness centrality is also known as + random-walk betweenness centrality [2]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + sources: list of nodes + Nodes to use as sources for current + + targets: list of nodes + Nodes to use as sinks for current + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : string or None, optional (default=None) + Key for edge data used as the edge weight. + If None, then use 1 as each edge weight. + The weight reflects the capacity or the strength of the + edge. + + dtype: data type (float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dict + Dictionary of edge tuples with betweenness centrality as the value. + + See Also + -------- + betweenness_centrality + edge_betweenness_centrality + current_flow_betweenness_centrality + + Notes + ----- + Current-flow betweenness can be computed in $O(I(n-1)+mn \log n)$ + time [1]_, where $I(n-1)$ is the time needed to compute the + inverse Laplacian. For a full matrix this is $O(n^3)$ but using + sparse methods you can achieve $O(nm{\sqrt k})$ where $k$ is the + Laplacian matrix condition number. + + The space required is $O(nw)$ where $w$ is the width of the sparse + Laplacian matrix. Worse case is $w=n$ for $O(n^2)$. + + If the edges have a 'weight' attribute they will be used as + weights in this algorithm. Unspecified weights are set to 1. + + References + ---------- + .. [1] Centrality Measures Based on Current Flow. + Ulrik Brandes and Daniel Fleischer, + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + + .. [2] A measure of betweenness centrality based on random walks, + M. E. J. Newman, Social Networks 27, 39-54 (2005). + """ + import numpy as np + + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + N = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + mapping = dict(zip(ordering, range(N))) + H = nx.relabel_nodes(G, mapping) + edges = (tuple(sorted((u, v))) for u, v in H.edges()) + betweenness = dict.fromkeys(edges, 0.0) + if normalized: + nb = (N - 1.0) * (N - 2.0) # normalization factor + else: + nb = 2.0 + for row, (e) in flow_matrix_row(H, weight=weight, dtype=dtype, solver=solver): + for ss in sources: + i = mapping[ss] + for tt in targets: + j = mapping[tt] + betweenness[e] += 0.5 * abs(row.item(i) - row.item(j)) + betweenness[e] /= nb + return {(ordering[s], ordering[t]): value for (s, t), value in betweenness.items()} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_closeness.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_closeness.py new file mode 100644 index 0000000000000000000000000000000000000000..67f86397bdcd61b344256b2b4c08f2c21986e05a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/current_flow_closeness.py @@ -0,0 +1,96 @@ +"""Current-flow closeness centrality measures.""" + +import networkx as nx +from networkx.algorithms.centrality.flow_matrix import ( + CGInverseLaplacian, + FullInverseLaplacian, + SuperLUInverseLaplacian, +) +from networkx.utils import not_implemented_for, reverse_cuthill_mckee_ordering + +__all__ = ["current_flow_closeness_centrality", "information_centrality"] + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def current_flow_closeness_centrality(G, weight=None, dtype=float, solver="lu"): + """Compute current-flow closeness centrality for nodes. + + Current-flow closeness centrality is variant of closeness + centrality based on effective resistance between nodes in + a network. This metric is also known as information centrality. + + Parameters + ---------- + G : graph + A NetworkX graph. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + The weight reflects the capacity or the strength of the + edge. + + dtype: data type (default=float) + Default data type for internal matrices. + Set to np.float32 for lower memory consumption. + + solver: string (default='lu') + Type of linear solver to use for computing the flow matrix. + Options are "full" (uses most memory), "lu" (recommended), and + "cg" (uses least memory). + + Returns + ------- + nodes : dictionary + Dictionary of nodes with current flow closeness centrality as the value. + + See Also + -------- + closeness_centrality + + Notes + ----- + The algorithm is from Brandes [1]_. + + See also [2]_ for the original definition of information centrality. + + References + ---------- + .. [1] Ulrik Brandes and Daniel Fleischer, + Centrality Measures Based on Current Flow. + Proc. 22nd Symp. Theoretical Aspects of Computer Science (STACS '05). + LNCS 3404, pp. 533-544. Springer-Verlag, 2005. + https://doi.org/10.1007/978-3-540-31856-9_44 + + .. [2] Karen Stephenson and Marvin Zelen: + Rethinking centrality: Methods and examples. + Social Networks 11(1):1-37, 1989. + https://doi.org/10.1016/0378-8733(89)90016-6 + """ + if not nx.is_connected(G): + raise nx.NetworkXError("Graph not connected.") + solvername = { + "full": FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian, + } + N = G.number_of_nodes() + ordering = list(reverse_cuthill_mckee_ordering(G)) + # make a copy with integer labels according to rcm ordering + # this could be done without a copy if we really wanted to + H = nx.relabel_nodes(G, dict(zip(ordering, range(N)))) + betweenness = dict.fromkeys(H, 0.0) # b[n]=0 for n in H + N = H.number_of_nodes() + L = nx.laplacian_matrix(H, nodelist=range(N), weight=weight).asformat("csc") + L = L.astype(dtype) + C2 = solvername[solver](L, width=1, dtype=dtype) # initialize solver + for v in H: + col = C2.get_row(v) + for w in H: + betweenness[v] += col.item(v) - 2 * col.item(w) + betweenness[w] += col.item(v) + return {ordering[node]: 1 / value for node, value in betweenness.items()} + + +information_centrality = current_flow_closeness_centrality diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/degree_alg.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/degree_alg.py new file mode 100644 index 0000000000000000000000000000000000000000..b3c1e321be3f9f3febce5a9104bde09924847001 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/degree_alg.py @@ -0,0 +1,150 @@ +"""Degree centrality measures.""" + +import networkx as nx +from networkx.utils.decorators import not_implemented_for + +__all__ = ["degree_centrality", "in_degree_centrality", "out_degree_centrality"] + + +@nx._dispatchable +def degree_centrality(G): + """Compute the degree centrality for nodes. + + The degree centrality for a node v is the fraction of nodes it + is connected to. + + Parameters + ---------- + G : graph + A networkx graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with degree centrality as the value. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)]) + >>> nx.degree_centrality(G) + {0: 1.0, 1: 1.0, 2: 0.6666666666666666, 3: 0.6666666666666666} + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + if len(G) <= 1: + return {n: 1 for n in G} + + s = 1.0 / (len(G) - 1.0) + centrality = {n: d * s for n, d in G.degree()} + return centrality + + +@not_implemented_for("undirected") +@nx._dispatchable +def in_degree_centrality(G): + """Compute the in-degree centrality for nodes. + + The in-degree centrality for a node v is the fraction of nodes its + incoming edges are connected to. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with in-degree centrality as values. + + Raises + ------ + NetworkXNotImplemented + If G is undirected. + + Examples + -------- + >>> G = nx.DiGraph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)]) + >>> nx.in_degree_centrality(G) + {0: 0.0, 1: 0.3333333333333333, 2: 0.6666666666666666, 3: 0.6666666666666666} + + See Also + -------- + degree_centrality, out_degree_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + if len(G) <= 1: + return {n: 1 for n in G} + + s = 1.0 / (len(G) - 1.0) + centrality = {n: d * s for n, d in G.in_degree()} + return centrality + + +@not_implemented_for("undirected") +@nx._dispatchable +def out_degree_centrality(G): + """Compute the out-degree centrality for nodes. + + The out-degree centrality for a node v is the fraction of nodes its + outgoing edges are connected to. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with out-degree centrality as values. + + Raises + ------ + NetworkXNotImplemented + If G is undirected. + + Examples + -------- + >>> G = nx.DiGraph([(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)]) + >>> nx.out_degree_centrality(G) + {0: 1.0, 1: 0.6666666666666666, 2: 0.0, 3: 0.0} + + See Also + -------- + degree_centrality, in_degree_centrality + + Notes + ----- + The degree centrality values are normalized by dividing by the maximum + possible degree in a simple graph n-1 where n is the number of nodes in G. + + For multigraphs or graphs with self loops the maximum degree might + be higher than n-1 and values of degree centrality greater than 1 + are possible. + """ + if len(G) <= 1: + return {n: 1 for n in G} + + s = 1.0 / (len(G) - 1.0) + centrality = {n: d * s for n, d in G.out_degree()} + return centrality diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/dispersion.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/dispersion.py new file mode 100644 index 0000000000000000000000000000000000000000..a3fa68583a9d18a40e6fbd4c8267e25f7a13c60a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/dispersion.py @@ -0,0 +1,107 @@ +from itertools import combinations + +import networkx as nx + +__all__ = ["dispersion"] + + +@nx._dispatchable +def dispersion(G, u=None, v=None, normalized=True, alpha=1.0, b=0.0, c=0.0): + r"""Calculate dispersion between `u` and `v` in `G`. + + A link between two actors (`u` and `v`) has a high dispersion when their + mutual ties (`s` and `t`) are not well connected with each other. + + Parameters + ---------- + G : graph + A NetworkX graph. + u : node, optional + The source for the dispersion score (e.g. ego node of the network). + v : node, optional + The target of the dispersion score if specified. + normalized : bool + If True (default) normalize by the embeddedness of the nodes (u and v). + alpha, b, c : float + Parameters for the normalization procedure. When `normalized` is True, + the dispersion value is normalized by:: + + result = ((dispersion + b) ** alpha) / (embeddedness + c) + + as long as the denominator is nonzero. + + Returns + ------- + nodes : dictionary + If u (v) is specified, returns a dictionary of nodes with dispersion + score for all "target" ("source") nodes. If neither u nor v is + specified, returns a dictionary of dictionaries for all nodes 'u' in the + graph with a dispersion score for each node 'v'. + + Notes + ----- + This implementation follows Lars Backstrom and Jon Kleinberg [1]_. Typical + usage would be to run dispersion on the ego network $G_u$ if $u$ were + specified. Running :func:`dispersion` with neither $u$ nor $v$ specified + can take some time to complete. + + References + ---------- + .. [1] Romantic Partnerships and the Dispersion of Social Ties: + A Network Analysis of Relationship Status on Facebook. + Lars Backstrom, Jon Kleinberg. + https://arxiv.org/pdf/1310.6753v1.pdf + + """ + + def _dispersion(G_u, u, v): + """dispersion for all nodes 'v' in a ego network G_u of node 'u'""" + u_nbrs = set(G_u[u]) + ST = {n for n in G_u[v] if n in u_nbrs} + set_uv = {u, v} + # all possible ties of connections that u and b share + possib = combinations(ST, 2) + total = 0 + for s, t in possib: + # neighbors of s that are in G_u, not including u and v + nbrs_s = u_nbrs.intersection(G_u[s]) - set_uv + # s and t are not directly connected + if t not in nbrs_s: + # s and t do not share a connection + if nbrs_s.isdisjoint(G_u[t]): + # tick for disp(u, v) + total += 1 + # neighbors that u and v share + embeddedness = len(ST) + + dispersion_val = total + if normalized: + dispersion_val = (total + b) ** alpha + if embeddedness + c != 0: + dispersion_val /= embeddedness + c + + return dispersion_val + + if u is None: + # v and u are not specified + if v is None: + results = {n: {} for n in G} + for u in G: + for v in G[u]: + results[u][v] = _dispersion(G, u, v) + # u is not specified, but v is + else: + results = dict.fromkeys(G[v], {}) + for u in G[v]: + results[u] = _dispersion(G, v, u) + else: + # u is specified with no target v + if v is None: + results = dict.fromkeys(G[u], {}) + for v in G[u]: + results[v] = _dispersion(G, u, v) + # both u and v are specified + else: + results = _dispersion(G, u, v) + + return results diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/eigenvector.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/eigenvector.py new file mode 100644 index 0000000000000000000000000000000000000000..b8cf63e8dc3562df2d570c3590501a51654367ff --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/eigenvector.py @@ -0,0 +1,357 @@ +"""Functions for computing eigenvector centrality.""" + +import math + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["eigenvector_centrality", "eigenvector_centrality_numpy"] + + +@not_implemented_for("multigraph") +@nx._dispatchable(edge_attrs="weight") +def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, weight=None): + r"""Compute the eigenvector centrality for the graph G. + + Eigenvector centrality computes the centrality for a node by adding + the centrality of its predecessors. The centrality for node $i$ is the + $i$-th element of a left eigenvector associated with the eigenvalue $\lambda$ + of maximum modulus that is positive. Such an eigenvector $x$ is + defined up to a multiplicative constant by the equation + + .. math:: + + \lambda x^T = x^T A, + + where $A$ is the adjacency matrix of the graph G. By definition of + row-column product, the equation above is equivalent to + + .. math:: + + \lambda x_i = \sum_{j\to i}x_j. + + That is, adding the eigenvector centralities of the predecessors of + $i$ one obtains the eigenvector centrality of $i$ multiplied by + $\lambda$. In the case of undirected graphs, $x$ also solves the familiar + right-eigenvector equation $Ax = \lambda x$. + + By virtue of the Perron–Frobenius theorem [1]_, if G is strongly + connected there is a unique eigenvector $x$, and all its entries + are strictly positive. + + If G is not strongly connected there might be several left + eigenvectors associated with $\lambda$, and some of their elements + might be zero. + + Parameters + ---------- + G : graph + A networkx graph. + + max_iter : integer, optional (default=100) + Maximum number of power iterations. + + tol : float, optional (default=1.0e-6) + Error tolerance (in Euclidean norm) used to check convergence in + power iteration. + + nstart : dictionary, optional (default=None) + Starting value of power iteration for each node. Must have a nonzero + projection on the desired eigenvector for the power method to converge. + If None, this implementation uses an all-ones vector, which is a safe + choice. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. Otherwise holds the + name of the edge attribute used as weight. In this measure the + weight is interpreted as the connection strength. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with eigenvector centrality as the value. The + associated vector has unit Euclidean norm and the values are + nonegative. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> centrality = nx.eigenvector_centrality(G) + >>> sorted((v, f"{c:0.2f}") for v, c in centrality.items()) + [(0, '0.37'), (1, '0.60'), (2, '0.60'), (3, '0.37')] + + Raises + ------ + NetworkXPointlessConcept + If the graph G is the null graph. + + NetworkXError + If each value in `nstart` is zero. + + PowerIterationFailedConvergence + If the algorithm fails to converge to the specified tolerance + within the specified number of iterations of the power iteration + method. + + See Also + -------- + eigenvector_centrality_numpy + :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank` + :func:`~networkx.algorithms.link_analysis.hits_alg.hits` + + Notes + ----- + Eigenvector centrality was introduced by Landau [2]_ for chess + tournaments. It was later rediscovered by Wei [3]_ and then + popularized by Kendall [4]_ in the context of sport ranking. Berge + introduced a general definition for graphs based on social connections + [5]_. Bonacich [6]_ reintroduced again eigenvector centrality and made + it popular in link analysis. + + This function computes the left dominant eigenvector, which corresponds + to adding the centrality of predecessors: this is the usual approach. + To add the centrality of successors first reverse the graph with + ``G.reverse()``. + + The implementation uses power iteration [7]_ to compute a dominant + eigenvector starting from the provided vector `nstart`. Convergence is + guaranteed as long as `nstart` has a nonzero projection on a dominant + eigenvector, which certainly happens using the default value. + + The method stops when the change in the computed vector between two + iterations is smaller than an error tolerance of ``G.number_of_nodes() + * tol`` or after ``max_iter`` iterations, but in the second case it + raises an exception. + + This implementation uses $(A + I)$ rather than the adjacency matrix + $A$ because the change preserves eigenvectors, but it shifts the + spectrum, thus guaranteeing convergence even for networks with + negative eigenvalues of maximum modulus. + + References + ---------- + .. [1] Abraham Berman and Robert J. Plemmons. + "Nonnegative Matrices in the Mathematical Sciences." + Classics in Applied Mathematics. SIAM, 1994. + + .. [2] Edmund Landau. + "Zur relativen Wertbemessung der Turnierresultate." + Deutsches Wochenschach, 11:366–369, 1895. + + .. [3] Teh-Hsing Wei. + "The Algebraic Foundations of Ranking Theory." + PhD thesis, University of Cambridge, 1952. + + .. [4] Maurice G. Kendall. + "Further contributions to the theory of paired comparisons." + Biometrics, 11(1):43–62, 1955. + https://www.jstor.org/stable/3001479 + + .. [5] Claude Berge + "Théorie des graphes et ses applications." + Dunod, Paris, France, 1958. + + .. [6] Phillip Bonacich. + "Technique for analyzing overlapping memberships." + Sociological Methodology, 4:176–185, 1972. + https://www.jstor.org/stable/270732 + + .. [7] Power iteration:: https://en.wikipedia.org/wiki/Power_iteration + + """ + if len(G) == 0: + raise nx.NetworkXPointlessConcept( + "cannot compute centrality for the null graph" + ) + # If no initial vector is provided, start with the all-ones vector. + if nstart is None: + nstart = {v: 1 for v in G} + if all(v == 0 for v in nstart.values()): + raise nx.NetworkXError("initial vector cannot have all zero values") + # Normalize the initial vector so that each entry is in [0, 1]. This is + # guaranteed to never have a divide-by-zero error by the previous line. + nstart_sum = sum(nstart.values()) + x = {k: v / nstart_sum for k, v in nstart.items()} + nnodes = G.number_of_nodes() + # make up to max_iter iterations + for _ in range(max_iter): + xlast = x + x = xlast.copy() # Start with xlast times I to iterate with (A+I) + # do the multiplication y^T = x^T A (left eigenvector) + for n in x: + for nbr in G[n]: + w = G[n][nbr].get(weight, 1) if weight else 1 + x[nbr] += xlast[n] * w + # Normalize the vector. The normalization denominator `norm` + # should never be zero by the Perron--Frobenius + # theorem. However, in case it is due to numerical error, we + # assume the norm to be one instead. + norm = math.hypot(*x.values()) or 1 + x = {k: v / norm for k, v in x.items()} + # Check for convergence (in the L_1 norm). + if sum(abs(x[n] - xlast[n]) for n in x) < nnodes * tol: + return x + raise nx.PowerIterationFailedConvergence(max_iter) + + +@nx._dispatchable(edge_attrs="weight") +def eigenvector_centrality_numpy(G, weight=None, max_iter=50, tol=0): + r"""Compute the eigenvector centrality for the graph `G`. + + Eigenvector centrality computes the centrality for a node by adding + the centrality of its predecessors. The centrality for node $i$ is the + $i$-th element of a left eigenvector associated with the eigenvalue $\lambda$ + of maximum modulus that is positive. Such an eigenvector $x$ is + defined up to a multiplicative constant by the equation + + .. math:: + + \lambda x^T = x^T A, + + where $A$ is the adjacency matrix of the graph `G`. By definition of + row-column product, the equation above is equivalent to + + .. math:: + + \lambda x_i = \sum_{j\to i}x_j. + + That is, adding the eigenvector centralities of the predecessors of + $i$ one obtains the eigenvector centrality of $i$ multiplied by + $\lambda$. In the case of undirected graphs, $x$ also solves the familiar + right-eigenvector equation $Ax = \lambda x$. + + By virtue of the Perron--Frobenius theorem [1]_, if `G` is (strongly) + connected, there is a unique eigenvector $x$, and all its entries + are strictly positive. + + However, if `G` is not (strongly) connected, there might be several left + eigenvectors associated with $\lambda$, and some of their elements + might be zero. + Depending on the method used to choose eigenvectors, round-off error can affect + which of the infinitely many eigenvectors is reported. + This can lead to inconsistent results for the same graph, + which the underlying implementation is not robust to. + For this reason, only (strongly) connected graphs are accepted. + + Parameters + ---------- + G : graph + A connected NetworkX graph. + + weight : None or string, optional (default=None) + If ``None``, all edge weights are considered equal. Otherwise holds the + name of the edge attribute used as weight. In this measure the + weight is interpreted as the connection strength. + + max_iter : integer, optional (default=50) + Maximum number of Arnoldi update iterations allowed. + + tol : float, optional (default=0) + Relative accuracy for eigenvalues (stopping criterion). + The default value of 0 implies machine precision. + + Returns + ------- + nodes : dict of nodes + Dictionary of nodes with eigenvector centrality as the value. The + associated vector has unit Euclidean norm and the values are + nonnegative. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> centrality = nx.eigenvector_centrality_numpy(G) + >>> print([f"{node} {centrality[node]:0.2f}" for node in centrality]) + ['0 0.37', '1 0.60', '2 0.60', '3 0.37'] + + Raises + ------ + NetworkXPointlessConcept + If the graph `G` is the null graph. + + ArpackNoConvergence + When the requested convergence is not obtained. The currently + converged eigenvalues and eigenvectors can be found as + eigenvalues and eigenvectors attributes of the exception object. + + AmbiguousSolution + If `G` is not connected. + + See Also + -------- + :func:`scipy.sparse.linalg.eigs` + eigenvector_centrality + :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank` + :func:`~networkx.algorithms.link_analysis.hits_alg.hits` + + Notes + ----- + Eigenvector centrality was introduced by Landau [2]_ for chess + tournaments. It was later rediscovered by Wei [3]_ and then + popularized by Kendall [4]_ in the context of sport ranking. Berge + introduced a general definition for graphs based on social connections + [5]_. Bonacich [6]_ reintroduced again eigenvector centrality and made + it popular in link analysis. + + This function computes the left dominant eigenvector, which corresponds + to adding the centrality of predecessors: this is the usual approach. + To add the centrality of successors first reverse the graph with + ``G.reverse()``. + + This implementation uses the + :func:`SciPy sparse eigenvalue solver` (ARPACK) + to find the largest eigenvalue/eigenvector pair using Arnoldi iterations + [7]_. + + References + ---------- + .. [1] Abraham Berman and Robert J. Plemmons. + "Nonnegative Matrices in the Mathematical Sciences". + Classics in Applied Mathematics. SIAM, 1994. + + .. [2] Edmund Landau. + "Zur relativen Wertbemessung der Turnierresultate". + Deutsches Wochenschach, 11:366--369, 1895. + + .. [3] Teh-Hsing Wei. + "The Algebraic Foundations of Ranking Theory". + PhD thesis, University of Cambridge, 1952. + + .. [4] Maurice G. Kendall. + "Further contributions to the theory of paired comparisons". + Biometrics, 11(1):43--62, 1955. + https://www.jstor.org/stable/3001479 + + .. [5] Claude Berge. + "Théorie des graphes et ses applications". + Dunod, Paris, France, 1958. + + .. [6] Phillip Bonacich. + "Technique for analyzing overlapping memberships". + Sociological Methodology, 4:176--185, 1972. + https://www.jstor.org/stable/270732 + + .. [7] Arnoldi, W. E. (1951). + "The principle of minimized iterations in the solution of the matrix eigenvalue problem". + Quarterly of Applied Mathematics. 9 (1): 17--29. + https://doi.org/10.1090/qam/42792 + """ + import numpy as np + import scipy as sp + + if len(G) == 0: + raise nx.NetworkXPointlessConcept( + "cannot compute centrality for the null graph" + ) + connected = nx.is_strongly_connected(G) if G.is_directed() else nx.is_connected(G) + if not connected: # See gh-6888. + raise nx.AmbiguousSolution( + "`eigenvector_centrality_numpy` does not give consistent results for disconnected graphs" + ) + M = nx.to_scipy_sparse_array(G, nodelist=list(G), weight=weight, dtype=float) + _, eigenvector = sp.sparse.linalg.eigs( + M.T, k=1, which="LR", maxiter=max_iter, tol=tol + ) + largest = eigenvector.flatten().real + norm = np.sign(largest.sum()) * sp.linalg.norm(largest) + return dict(zip(G, (largest / norm).tolist())) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/flow_matrix.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/flow_matrix.py new file mode 100644 index 0000000000000000000000000000000000000000..e72b5e976c003c9e870f0c17e0fea25bb6e0596a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/flow_matrix.py @@ -0,0 +1,130 @@ +# Helpers for current-flow betweenness and current-flow closeness +# Lazy computations for inverse Laplacian and flow-matrix rows. +import networkx as nx + + +@nx._dispatchable(edge_attrs="weight") +def flow_matrix_row(G, weight=None, dtype=float, solver="lu"): + # Generate a row of the current-flow matrix + import numpy as np + + solvername = { + "full": FullInverseLaplacian, + "lu": SuperLUInverseLaplacian, + "cg": CGInverseLaplacian, + } + n = G.number_of_nodes() + L = nx.laplacian_matrix(G, nodelist=range(n), weight=weight).asformat("csc") + L = L.astype(dtype) + C = solvername[solver](L, dtype=dtype) # initialize solver + w = C.w # w is the Laplacian matrix width + # row-by-row flow matrix + for u, v in sorted(sorted((u, v)) for u, v in G.edges()): + B = np.zeros(w, dtype=dtype) + c = G[u][v].get(weight, 1.0) + B[u % w] = c + B[v % w] = -c + # get only the rows needed in the inverse laplacian + # and multiply to get the flow matrix row + row = B @ C.get_rows(u, v) + yield row, (u, v) + + +# Class to compute the inverse laplacian only for specified rows +# Allows computation of the current-flow matrix without storing entire +# inverse laplacian matrix +class InverseLaplacian: + def __init__(self, L, width=None, dtype=None): + global np + import numpy as np + + (n, n) = L.shape + self.dtype = dtype + self.n = n + if width is None: + self.w = self.width(L) + else: + self.w = width + self.C = np.zeros((self.w, n), dtype=dtype) + self.L1 = L[1:, 1:] + self.init_solver(L) + + def init_solver(self, L): + pass + + def solve(self, r): + raise nx.NetworkXError("Implement solver") + + def solve_inverse(self, r): + raise nx.NetworkXError("Implement solver") + + def get_rows(self, r1, r2): + for r in range(r1, r2 + 1): + self.C[r % self.w, 1:] = self.solve_inverse(r) + return self.C + + def get_row(self, r): + self.C[r % self.w, 1:] = self.solve_inverse(r) + return self.C[r % self.w] + + def width(self, L): + m = 0 + for i, row in enumerate(L): + w = 0 + y = np.nonzero(row)[-1] + if len(y) > 0: + v = y - i + w = v.max() - v.min() + 1 + m = max(w, m) + return m + + +class FullInverseLaplacian(InverseLaplacian): + def init_solver(self, L): + self.IL = np.zeros(L.shape, dtype=self.dtype) + self.IL[1:, 1:] = np.linalg.inv(self.L1.todense()) + + def solve(self, rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s = self.IL @ rhs + return s + + def solve_inverse(self, r): + return self.IL[r, 1:] + + +class SuperLUInverseLaplacian(InverseLaplacian): + def init_solver(self, L): + import scipy as sp + + self.lusolve = sp.sparse.linalg.factorized(self.L1.tocsc()) + + def solve_inverse(self, r): + rhs = np.zeros(self.n, dtype=self.dtype) + rhs[r] = 1 + return self.lusolve(rhs[1:]) + + def solve(self, rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s[1:] = self.lusolve(rhs[1:]) + return s + + +class CGInverseLaplacian(InverseLaplacian): + def init_solver(self, L): + global sp + import scipy as sp + + ilu = sp.sparse.linalg.spilu(self.L1.tocsc()) + n = self.n - 1 + self.M = sp.sparse.linalg.LinearOperator(shape=(n, n), matvec=ilu.solve) + + def solve(self, rhs): + s = np.zeros(rhs.shape, dtype=self.dtype) + s[1:] = sp.sparse.linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0] + return s + + def solve_inverse(self, r): + rhs = np.zeros(self.n, self.dtype) + rhs[r] = 1 + return sp.sparse.linalg.cg(self.L1, rhs[1:], M=self.M, atol=0)[0] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/group.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/group.py new file mode 100644 index 0000000000000000000000000000000000000000..cff1d4719663d8eef6b4e7e255a1f319b4c2e499 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/group.py @@ -0,0 +1,787 @@ +"""Group centrality measures.""" + +from copy import deepcopy + +import networkx as nx +from networkx.algorithms.centrality.betweenness import ( + _accumulate_endpoints, + _single_source_dijkstra_path_basic, + _single_source_shortest_path_basic, +) +from networkx.utils.decorators import not_implemented_for + +__all__ = [ + "group_betweenness_centrality", + "group_closeness_centrality", + "group_degree_centrality", + "group_in_degree_centrality", + "group_out_degree_centrality", + "prominent_group", +] + + +@nx._dispatchable(edge_attrs="weight") +def group_betweenness_centrality(G, C, normalized=True, weight=None, endpoints=False): + r"""Compute the group betweenness centrality for a group of nodes. + + Group betweenness centrality of a group of nodes $C$ is the sum of the + fraction of all-pairs shortest paths that pass through any vertex in $C$ + + .. math:: + + c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)} + + where $V$ is the set of nodes, $\sigma(s, t)$ is the number of + shortest $(s, t)$-paths, and $\sigma(s, t|C)$ is the number of + those paths passing through some node in group $C$. Note that + $(s, t)$ are not members of the group ($V-C$ is the set of nodes + in $V$ that are not in $C$). + + Parameters + ---------- + G : graph + A NetworkX graph. + + C : list or set or list of lists or list of sets + A group or a list of groups containing nodes which belong to G, for which group betweenness + centrality is to be calculated. + + normalized : bool, optional (default=True) + If True, group betweenness is normalized by `1/((|V|-|C|)(|V|-|C|-1))` + where `|V|` is the number of nodes in G and `|C|` is the number of nodes in C. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + The weight of an edge is treated as the length or distance between the two sides. + + endpoints : bool, optional (default=False) + If True include the endpoints in the shortest path counts. + + Raises + ------ + NodeNotFound + If node(s) in C are not present in G. + + Returns + ------- + betweenness : list of floats or float + If C is a single group then return a float. If C is a list with + several groups then return a list of group betweenness centralities. + + See Also + -------- + betweenness_centrality + + Notes + ----- + Group betweenness centrality is described in [1]_ and its importance discussed in [3]_. + The initial implementation of the algorithm is mentioned in [2]_. This function uses + an improved algorithm presented in [4]_. + + The number of nodes in the group must be a maximum of n - 2 where `n` + is the total number of nodes in the graph. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The total number of paths between source and target is counted + differently for directed and undirected graphs. Directed paths + between "u" and "v" are counted as two possible paths (one each + direction) while undirected paths between "u" and "v" are counted + as one path. Said another way, the sum in the expression above is + over all ``s != t`` for directed graphs and for ``s < t`` for undirected graphs. + + + References + ---------- + .. [1] M G Everett and S P Borgatti: + The Centrality of Groups and Classes. + Journal of Mathematical Sociology. 23(3): 181-201. 1999. + http://www.analytictech.com/borgatti/group_centrality.htm + .. [2] Ulrik Brandes: + On Variants of Shortest-Path Betweenness + Centrality and their Generic Computation. + Social Networks 30(2):136-145, 2008. + http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.9610&rep=rep1&type=pdf + .. [3] Sourav Medya et. al.: + Group Centrality Maximization via Network Design. + SIAM International Conference on Data Mining, SDM 2018, 126–134. + https://sites.cs.ucsb.edu/~arlei/pubs/sdm18.pdf + .. [4] Rami Puzis, Yuval Elovici, and Shlomi Dolev. + "Fast algorithm for successive computation of group betweenness centrality." + https://journals.aps.org/pre/pdf/10.1103/PhysRevE.76.056709 + + """ + GBC = [] # initialize betweenness + list_of_groups = True + # check weather C contains one or many groups + if any(el in G for el in C): + C = [C] + list_of_groups = False + set_v = {node for group in C for node in group} + if set_v - G.nodes: # element(s) of C not in G + raise nx.NodeNotFound(f"The node(s) {set_v - G.nodes} are in C but not in G.") + + # pre-processing + PB, sigma, D = _group_preprocessing(G, set_v, weight) + + # the algorithm for each group + for group in C: + group = set(group) # set of nodes in group + # initialize the matrices of the sigma and the PB + GBC_group = 0 + sigma_m = deepcopy(sigma) + PB_m = deepcopy(PB) + sigma_m_v = deepcopy(sigma_m) + PB_m_v = deepcopy(PB_m) + for v in group: + GBC_group += PB_m[v][v] + for x in group: + for y in group: + dxvy = 0 + dxyv = 0 + dvxy = 0 + if not ( + sigma_m[x][y] == 0 or sigma_m[x][v] == 0 or sigma_m[v][y] == 0 + ): + if D[x][v] == D[x][y] + D[y][v]: + dxyv = sigma_m[x][y] * sigma_m[y][v] / sigma_m[x][v] + if D[x][y] == D[x][v] + D[v][y]: + dxvy = sigma_m[x][v] * sigma_m[v][y] / sigma_m[x][y] + if D[v][y] == D[v][x] + D[x][y]: + dvxy = sigma_m[v][x] * sigma[x][y] / sigma[v][y] + sigma_m_v[x][y] = sigma_m[x][y] * (1 - dxvy) + PB_m_v[x][y] = PB_m[x][y] - PB_m[x][y] * dxvy + if y != v: + PB_m_v[x][y] -= PB_m[x][v] * dxyv + if x != v: + PB_m_v[x][y] -= PB_m[v][y] * dvxy + sigma_m, sigma_m_v = sigma_m_v, sigma_m + PB_m, PB_m_v = PB_m_v, PB_m + + # endpoints + v, c = len(G), len(group) + if not endpoints: + scale = 0 + # if the graph is connected then subtract the endpoints from + # the count for all the nodes in the graph. else count how many + # nodes are connected to the group's nodes and subtract that. + if nx.is_directed(G): + if nx.is_strongly_connected(G): + scale = c * (2 * v - c - 1) + elif nx.is_connected(G): + scale = c * (2 * v - c - 1) + if scale == 0: + for group_node1 in group: + for node in D[group_node1]: + if node != group_node1: + if node in group: + scale += 1 + else: + scale += 2 + GBC_group -= scale + + # normalized + if normalized: + scale = 1 / ((v - c) * (v - c - 1)) + GBC_group *= scale + + # If undirected than count only the undirected edges + elif not G.is_directed(): + GBC_group /= 2 + + GBC.append(GBC_group) + if list_of_groups: + return GBC + return GBC[0] + + +def _group_preprocessing(G, set_v, weight): + sigma = {} + delta = {} + D = {} + betweenness = dict.fromkeys(G, 0) + for s in G: + if weight is None: # use BFS + S, P, sigma[s], D[s] = _single_source_shortest_path_basic(G, s) + else: # use Dijkstra's algorithm + S, P, sigma[s], D[s] = _single_source_dijkstra_path_basic(G, s, weight) + betweenness, delta[s] = _accumulate_endpoints(betweenness, S, P, sigma[s], s) + for i in delta[s]: # add the paths from s to i and rescale sigma + if s != i: + delta[s][i] += 1 + if weight is not None: + sigma[s][i] = sigma[s][i] / 2 + # building the path betweenness matrix only for nodes that appear in the group + PB = dict.fromkeys(G) + for group_node1 in set_v: + PB[group_node1] = dict.fromkeys(G, 0.0) + for group_node2 in set_v: + if group_node2 not in D[group_node1]: + continue + for node in G: + # if node is connected to the two group nodes than continue + if group_node2 in D[node] and group_node1 in D[node]: + if ( + D[node][group_node2] + == D[node][group_node1] + D[group_node1][group_node2] + ): + PB[group_node1][group_node2] += ( + delta[node][group_node2] + * sigma[node][group_node1] + * sigma[group_node1][group_node2] + / sigma[node][group_node2] + ) + return PB, sigma, D + + +@nx._dispatchable(edge_attrs="weight") +def prominent_group( + G, k, weight=None, C=None, endpoints=False, normalized=True, greedy=False +): + r"""Find the prominent group of size $k$ in graph $G$. The prominence of the + group is evaluated by the group betweenness centrality. + + Group betweenness centrality of a group of nodes $C$ is the sum of the + fraction of all-pairs shortest paths that pass through any vertex in $C$ + + .. math:: + + c_B(v) =\sum_{s,t \in V} \frac{\sigma(s, t|v)}{\sigma(s, t)} + + where $V$ is the set of nodes, $\sigma(s, t)$ is the number of + shortest $(s, t)$-paths, and $\sigma(s, t|C)$ is the number of + those paths passing through some node in group $C$. Note that + $(s, t)$ are not members of the group ($V-C$ is the set of nodes + in $V$ that are not in $C$). + + Parameters + ---------- + G : graph + A NetworkX graph. + + k : int + The number of nodes in the group. + + normalized : bool, optional (default=True) + If True, group betweenness is normalized by ``1/((|V|-|C|)(|V|-|C|-1))`` + where ``|V|`` is the number of nodes in G and ``|C|`` is the number of + nodes in C. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + The weight of an edge is treated as the length or distance between the two sides. + + endpoints : bool, optional (default=False) + If True include the endpoints in the shortest path counts. + + C : list or set, optional (default=None) + list of nodes which won't be candidates of the prominent group. + + greedy : bool, optional (default=False) + Using a naive greedy algorithm in order to find non-optimal prominent + group. For scale free networks the results are negligibly below the optimal + results. + + Raises + ------ + NodeNotFound + If node(s) in C are not present in G. + + Returns + ------- + max_GBC : float + The group betweenness centrality of the prominent group. + + max_group : list + The list of nodes in the prominent group. + + See Also + -------- + betweenness_centrality, group_betweenness_centrality + + Notes + ----- + Group betweenness centrality is described in [1]_ and its importance discussed in [3]_. + The algorithm is described in [2]_ and is based on techniques mentioned in [4]_. + + The number of nodes in the group must be a maximum of ``n - 2`` where ``n`` + is the total number of nodes in the graph. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + The total number of paths between source and target is counted + differently for directed and undirected graphs. Directed paths + between "u" and "v" are counted as two possible paths (one each + direction) while undirected paths between "u" and "v" are counted + as one path. Said another way, the sum in the expression above is + over all ``s != t`` for directed graphs and for ``s < t`` for undirected graphs. + + References + ---------- + .. [1] M G Everett and S P Borgatti: + The Centrality of Groups and Classes. + Journal of Mathematical Sociology. 23(3): 181-201. 1999. + http://www.analytictech.com/borgatti/group_centrality.htm + .. [2] Rami Puzis, Yuval Elovici, and Shlomi Dolev: + "Finding the Most Prominent Group in Complex Networks" + AI communications 20(4): 287-296, 2007. + https://www.researchgate.net/profile/Rami_Puzis2/publication/220308855 + .. [3] Sourav Medya et. al.: + Group Centrality Maximization via Network Design. + SIAM International Conference on Data Mining, SDM 2018, 126–134. + https://sites.cs.ucsb.edu/~arlei/pubs/sdm18.pdf + .. [4] Rami Puzis, Yuval Elovici, and Shlomi Dolev. + "Fast algorithm for successive computation of group betweenness centrality." + https://journals.aps.org/pre/pdf/10.1103/PhysRevE.76.056709 + """ + import numpy as np + import pandas as pd + + if C is not None: + C = set(C) + if C - G.nodes: # element(s) of C not in G + raise nx.NodeNotFound(f"The node(s) {C - G.nodes} are in C but not in G.") + nodes = list(G.nodes - C) + else: + nodes = list(G.nodes) + DF_tree = nx.Graph() + DF_tree.__networkx_cache__ = None # Disable caching + PB, sigma, D = _group_preprocessing(G, nodes, weight) + betweenness = pd.DataFrame.from_dict(PB) + if C is not None: + for node in C: + # remove from the betweenness all the nodes not part of the group + betweenness = betweenness.drop(index=node) + betweenness = betweenness.drop(columns=node) + CL = [node for _, node in sorted(zip(np.diag(betweenness), nodes), reverse=True)] + max_GBC = 0 + max_group = [] + DF_tree.add_node( + 1, + CL=CL, + betweenness=betweenness, + GBC=0, + GM=[], + sigma=sigma, + cont=dict(zip(nodes, np.diag(betweenness))), + ) + + # the algorithm + DF_tree.nodes[1]["heu"] = 0 + for i in range(k): + DF_tree.nodes[1]["heu"] += DF_tree.nodes[1]["cont"][DF_tree.nodes[1]["CL"][i]] + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, 1, D, max_group, nodes, greedy + ) + + v = len(G) + if not endpoints: + scale = 0 + # if the graph is connected then subtract the endpoints from + # the count for all the nodes in the graph. else count how many + # nodes are connected to the group's nodes and subtract that. + if nx.is_directed(G): + if nx.is_strongly_connected(G): + scale = k * (2 * v - k - 1) + elif nx.is_connected(G): + scale = k * (2 * v - k - 1) + if scale == 0: + for group_node1 in max_group: + for node in D[group_node1]: + if node != group_node1: + if node in max_group: + scale += 1 + else: + scale += 2 + max_GBC -= scale + + # normalized + if normalized: + scale = 1 / ((v - k) * (v - k - 1)) + max_GBC *= scale + + # If undirected then count only the undirected edges + elif not G.is_directed(): + max_GBC /= 2 + max_GBC = float(f"{max_GBC:.2f}") + return max_GBC, max_group + + +def _dfbnb(G, k, DF_tree, max_GBC, root, D, max_group, nodes, greedy): + # stopping condition - if we found a group of size k and with higher GBC then prune + if len(DF_tree.nodes[root]["GM"]) == k and DF_tree.nodes[root]["GBC"] > max_GBC: + return DF_tree.nodes[root]["GBC"], DF_tree, DF_tree.nodes[root]["GM"] + # stopping condition - if the size of group members equal to k or there are less than + # k - |GM| in the candidate list or the heuristic function plus the GBC is below the + # maximal GBC found then prune + if ( + len(DF_tree.nodes[root]["GM"]) == k + or len(DF_tree.nodes[root]["CL"]) <= k - len(DF_tree.nodes[root]["GM"]) + or DF_tree.nodes[root]["GBC"] + DF_tree.nodes[root]["heu"] <= max_GBC + ): + return max_GBC, DF_tree, max_group + + # finding the heuristic of both children + node_p, node_m, DF_tree = _heuristic(k, root, DF_tree, D, nodes, greedy) + + # finding the child with the bigger heuristic + GBC and expand + # that node first if greedy then only expand the plus node + if greedy: + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, node_p, D, max_group, nodes, greedy + ) + + elif ( + DF_tree.nodes[node_p]["GBC"] + DF_tree.nodes[node_p]["heu"] + > DF_tree.nodes[node_m]["GBC"] + DF_tree.nodes[node_m]["heu"] + ): + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, node_p, D, max_group, nodes, greedy + ) + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, node_m, D, max_group, nodes, greedy + ) + else: + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, node_m, D, max_group, nodes, greedy + ) + max_GBC, DF_tree, max_group = _dfbnb( + G, k, DF_tree, max_GBC, node_p, D, max_group, nodes, greedy + ) + return max_GBC, DF_tree, max_group + + +def _heuristic(k, root, DF_tree, D, nodes, greedy): + import numpy as np + + # This helper function add two nodes to DF_tree - one left son and the + # other right son, finds their heuristic, CL, GBC, and GM + node_p = DF_tree.number_of_nodes() + 1 + node_m = DF_tree.number_of_nodes() + 2 + added_node = DF_tree.nodes[root]["CL"][0] + + # adding the plus node + DF_tree.add_nodes_from([(node_p, deepcopy(DF_tree.nodes[root]))]) + DF_tree.nodes[node_p]["GM"].append(added_node) + DF_tree.nodes[node_p]["GBC"] += DF_tree.nodes[node_p]["cont"][added_node] + root_node = DF_tree.nodes[root] + for x in nodes: + for y in nodes: + dxvy = 0 + dxyv = 0 + dvxy = 0 + if not ( + root_node["sigma"][x][y] == 0 + or root_node["sigma"][x][added_node] == 0 + or root_node["sigma"][added_node][y] == 0 + ): + if D[x][added_node] == D[x][y] + D[y][added_node]: + dxyv = ( + root_node["sigma"][x][y] + * root_node["sigma"][y][added_node] + / root_node["sigma"][x][added_node] + ) + if D[x][y] == D[x][added_node] + D[added_node][y]: + dxvy = ( + root_node["sigma"][x][added_node] + * root_node["sigma"][added_node][y] + / root_node["sigma"][x][y] + ) + if D[added_node][y] == D[added_node][x] + D[x][y]: + dvxy = ( + root_node["sigma"][added_node][x] + * root_node["sigma"][x][y] + / root_node["sigma"][added_node][y] + ) + DF_tree.nodes[node_p]["sigma"][x][y] = root_node["sigma"][x][y] * (1 - dxvy) + DF_tree.nodes[node_p]["betweenness"].loc[y, x] = ( + root_node["betweenness"][x][y] - root_node["betweenness"][x][y] * dxvy + ) + if y != added_node: + DF_tree.nodes[node_p]["betweenness"].loc[y, x] -= ( + root_node["betweenness"][x][added_node] * dxyv + ) + if x != added_node: + DF_tree.nodes[node_p]["betweenness"].loc[y, x] -= ( + root_node["betweenness"][added_node][y] * dvxy + ) + + DF_tree.nodes[node_p]["CL"] = [ + node + for _, node in sorted( + zip(np.diag(DF_tree.nodes[node_p]["betweenness"]), nodes), reverse=True + ) + if node not in DF_tree.nodes[node_p]["GM"] + ] + DF_tree.nodes[node_p]["cont"] = dict( + zip(nodes, np.diag(DF_tree.nodes[node_p]["betweenness"])) + ) + DF_tree.nodes[node_p]["heu"] = 0 + for i in range(k - len(DF_tree.nodes[node_p]["GM"])): + DF_tree.nodes[node_p]["heu"] += DF_tree.nodes[node_p]["cont"][ + DF_tree.nodes[node_p]["CL"][i] + ] + + # adding the minus node - don't insert the first node in the CL to GM + # Insert minus node only if isn't greedy type algorithm + if not greedy: + DF_tree.add_nodes_from([(node_m, deepcopy(DF_tree.nodes[root]))]) + DF_tree.nodes[node_m]["CL"].pop(0) + DF_tree.nodes[node_m]["cont"].pop(added_node) + DF_tree.nodes[node_m]["heu"] = 0 + for i in range(k - len(DF_tree.nodes[node_m]["GM"])): + DF_tree.nodes[node_m]["heu"] += DF_tree.nodes[node_m]["cont"][ + DF_tree.nodes[node_m]["CL"][i] + ] + else: + node_m = None + + return node_p, node_m, DF_tree + + +@nx._dispatchable(edge_attrs="weight") +def group_closeness_centrality(G, S, weight=None): + r"""Compute the group closeness centrality for a group of nodes. + + Group closeness centrality of a group of nodes $S$ is a measure + of how close the group is to the other nodes in the graph. + + .. math:: + + c_{close}(S) = \frac{|V-S|}{\sum_{v \in V-S} d_{S, v}} + + d_{S, v} = min_{u \in S} (d_{u, v}) + + where $V$ is the set of nodes, $d_{S, v}$ is the distance of + the group $S$ from $v$ defined as above. ($V-S$ is the set of nodes + in $V$ that are not in $S$). + + Parameters + ---------- + G : graph + A NetworkX graph. + + S : list or set + S is a group of nodes which belong to G, for which group closeness + centrality is to be calculated. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + The weight of an edge is treated as the length or distance between the two sides. + + Raises + ------ + NodeNotFound + If node(s) in S are not present in G. + + Returns + ------- + closeness : float + Group closeness centrality of the group S. + + See Also + -------- + closeness_centrality + + Notes + ----- + The measure was introduced in [1]_. + The formula implemented here is described in [2]_. + + Higher values of closeness indicate greater centrality. + + It is assumed that 1 / 0 is 0 (required in the case of directed graphs, + or when a shortest path length is 0). + + The number of nodes in the group must be a maximum of n - 1 where `n` + is the total number of nodes in the graph. + + For directed graphs, the incoming distance is utilized here. To use the + outward distance, act on `G.reverse()`. + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + References + ---------- + .. [1] M G Everett and S P Borgatti: + The Centrality of Groups and Classes. + Journal of Mathematical Sociology. 23(3): 181-201. 1999. + http://www.analytictech.com/borgatti/group_centrality.htm + .. [2] J. Zhao et. al.: + Measuring and Maximizing Group Closeness Centrality over + Disk Resident Graphs. + WWWConference Proceedings, 2014. 689-694. + https://doi.org/10.1145/2567948.2579356 + """ + if G.is_directed(): + G = G.reverse() # reverse view + closeness = 0 # initialize to 0 + V = set(G) # set of nodes in G + S = set(S) # set of nodes in group S + V_S = V - S # set of nodes in V but not S + shortest_path_lengths = nx.multi_source_dijkstra_path_length(G, S, weight=weight) + # accumulation + for v in V_S: + try: + closeness += shortest_path_lengths[v] + except KeyError: # no path exists + closeness += 0 + try: + closeness = len(V_S) / closeness + except ZeroDivisionError: # 1 / 0 assumed as 0 + closeness = 0 + return closeness + + +@nx._dispatchable +def group_degree_centrality(G, S): + """Compute the group degree centrality for a group of nodes. + + Group degree centrality of a group of nodes $S$ is the fraction + of non-group members connected to group members. + + Parameters + ---------- + G : graph + A NetworkX graph. + + S : list or set + S is a group of nodes which belong to G, for which group degree + centrality is to be calculated. + + Raises + ------ + NetworkXError + If node(s) in S are not in G. + + Returns + ------- + centrality : float + Group degree centrality of the group S. + + See Also + -------- + degree_centrality + group_in_degree_centrality + group_out_degree_centrality + + Notes + ----- + The measure was introduced in [1]_. + + The number of nodes in the group must be a maximum of n - 1 where `n` + is the total number of nodes in the graph. + + References + ---------- + .. [1] M G Everett and S P Borgatti: + The Centrality of Groups and Classes. + Journal of Mathematical Sociology. 23(3): 181-201. 1999. + http://www.analytictech.com/borgatti/group_centrality.htm + """ + centrality = len(set().union(*[set(G.neighbors(i)) for i in S]) - set(S)) + centrality /= len(G.nodes()) - len(S) + return centrality + + +@not_implemented_for("undirected") +@nx._dispatchable +def group_in_degree_centrality(G, S): + """Compute the group in-degree centrality for a group of nodes. + + Group in-degree centrality of a group of nodes $S$ is the fraction + of non-group members connected to group members by incoming edges. + + Parameters + ---------- + G : graph + A NetworkX graph. + + S : list or set + S is a group of nodes which belong to G, for which group in-degree + centrality is to be calculated. + + Returns + ------- + centrality : float + Group in-degree centrality of the group S. + + Raises + ------ + NetworkXNotImplemented + If G is undirected. + + NodeNotFound + If node(s) in S are not in G. + + See Also + -------- + degree_centrality + group_degree_centrality + group_out_degree_centrality + + Notes + ----- + The number of nodes in the group must be a maximum of n - 1 where `n` + is the total number of nodes in the graph. + + `G.neighbors(i)` gives nodes with an outward edge from i, in a DiGraph, + so for group in-degree centrality, the reverse graph is used. + """ + return group_degree_centrality(G.reverse(), S) + + +@not_implemented_for("undirected") +@nx._dispatchable +def group_out_degree_centrality(G, S): + """Compute the group out-degree centrality for a group of nodes. + + Group out-degree centrality of a group of nodes $S$ is the fraction + of non-group members connected to group members by outgoing edges. + + Parameters + ---------- + G : graph + A NetworkX graph. + + S : list or set + S is a group of nodes which belong to G, for which group in-degree + centrality is to be calculated. + + Returns + ------- + centrality : float + Group out-degree centrality of the group S. + + Raises + ------ + NetworkXNotImplemented + If G is undirected. + + NodeNotFound + If node(s) in S are not in G. + + See Also + -------- + degree_centrality + group_degree_centrality + group_in_degree_centrality + + Notes + ----- + The number of nodes in the group must be a maximum of n - 1 where `n` + is the total number of nodes in the graph. + + `G.neighbors(i)` gives nodes with an outward edge from i, in a DiGraph, + so for group out-degree centrality, the graph itself is used. + """ + return group_degree_centrality(G, S) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/harmonic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/harmonic.py new file mode 100644 index 0000000000000000000000000000000000000000..3d76f5f1f734cab7c420cc48e0000d05d85b7884 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/harmonic.py @@ -0,0 +1,88 @@ +"""Functions for computing the harmonic centrality of a graph.""" + +from functools import partial + +import networkx as nx + +__all__ = ["harmonic_centrality"] + + +@nx._dispatchable(edge_attrs="distance") +def harmonic_centrality(G, nbunch=None, distance=None, sources=None): + r"""Compute harmonic centrality for nodes. + + Harmonic centrality [1]_ of a node `u` is the sum of the reciprocal + of the shortest path distances from all other nodes to `u` + + .. math:: + + C(u) = \sum_{v \neq u} \frac{1}{d(v, u)} + + where `d(v, u)` is the shortest-path distance between `v` and `u`. + + If `sources` is given as an argument, the returned harmonic centrality + values are calculated as the sum of the reciprocals of the shortest + path distances from the nodes specified in `sources` to `u` instead + of from all nodes to `u`. + + Notice that higher values indicate higher centrality. + + Parameters + ---------- + G : graph + A NetworkX graph + + nbunch : container (default: all nodes in G) + Container of nodes for which harmonic centrality values are calculated. + + sources : container (default: all nodes in G) + Container of nodes `v` over which reciprocal distances are computed. + Nodes not in `G` are silently ignored. + + distance : edge attribute key, optional (default=None) + Use the specified edge attribute as the edge distance in shortest + path calculations. If `None`, then each edge will have distance equal to 1. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with harmonic centrality as the value. + + See Also + -------- + betweenness_centrality, load_centrality, eigenvector_centrality, + degree_centrality, closeness_centrality + + Notes + ----- + If the 'distance' keyword is set to an edge attribute key then the + shortest-path length will be computed using Dijkstra's algorithm with + that edge attribute as the edge weight. + + References + ---------- + .. [1] Boldi, Paolo, and Sebastiano Vigna. "Axioms for centrality." + Internet Mathematics 10.3-4 (2014): 222-262. + """ + + nbunch = set(G.nbunch_iter(nbunch) if nbunch is not None else G.nodes) + sources = set(G.nbunch_iter(sources) if sources is not None else G.nodes) + + centrality = {u: 0 for u in nbunch} + + transposed = False + if len(nbunch) < len(sources): + transposed = True + nbunch, sources = sources, nbunch + if nx.is_directed(G): + G = nx.reverse(G, copy=False) + + spl = partial(nx.shortest_path_length, G, weight=distance) + for v in sources: + dist = spl(v) + for u, d_uv in dist.items(): + # Ignore self-loops and edges with 0 weight + if d_uv != 0 and u in nbunch: + centrality[v if transposed else u] += 1 / d_uv + + return centrality diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/katz.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/katz.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd087bc3e55de4f71413033f969ad22e8acddd7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/katz.py @@ -0,0 +1,331 @@ +"""Katz centrality.""" + +import math + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["katz_centrality", "katz_centrality_numpy"] + + +@not_implemented_for("multigraph") +@nx._dispatchable(edge_attrs="weight") +def katz_centrality( + G, + alpha=0.1, + beta=1.0, + max_iter=1000, + tol=1.0e-6, + nstart=None, + normalized=True, + weight=None, +): + r"""Compute the Katz centrality for the nodes of the graph G. + + Katz centrality computes the centrality for a node based on the centrality + of its neighbors. It is a generalization of the eigenvector centrality. The + Katz centrality for node $i$ is + + .. math:: + + x_i = \alpha \sum_{j} A_{ij} x_j + \beta, + + where $A$ is the adjacency matrix of graph G with eigenvalues $\lambda$. + + The parameter $\beta$ controls the initial centrality and + + .. math:: + + \alpha < \frac{1}{\lambda_{\max}}. + + Katz centrality computes the relative influence of a node within a + network by measuring the number of the immediate neighbors (first + degree nodes) and also all other nodes in the network that connect + to the node under consideration through these immediate neighbors. + + Extra weight can be provided to immediate neighbors through the + parameter $\beta$. Connections made with distant neighbors + are, however, penalized by an attenuation factor $\alpha$ which + should be strictly less than the inverse largest eigenvalue of the + adjacency matrix in order for the Katz centrality to be computed + correctly. More information is provided in [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph. + + alpha : float, optional (default=0.1) + Attenuation factor + + beta : scalar or dictionary, optional (default=1.0) + Weight attributed to the immediate neighborhood. If not a scalar, the + dictionary must have a value for every node. + + max_iter : integer, optional (default=1000) + Maximum number of iterations in power method. + + tol : float, optional (default=1.0e-6) + Error tolerance used to check convergence in power method iteration. + + nstart : dictionary, optional + Starting value of Katz iteration for each node. + + normalized : bool, optional (default=True) + If True normalize the resulting values. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + In this measure the weight is interpreted as the connection strength. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with Katz centrality as the value. + + Raises + ------ + NetworkXError + If the parameter `beta` is not a scalar but lacks a value for at least + one node + + PowerIterationFailedConvergence + If the algorithm fails to converge to the specified tolerance + within the specified number of iterations of the power iteration + method. + + Examples + -------- + >>> import math + >>> G = nx.path_graph(4) + >>> phi = (1 + math.sqrt(5)) / 2.0 # largest eigenvalue of adj matrix + >>> centrality = nx.katz_centrality(G, 1 / phi - 0.01) + >>> for n, c in sorted(centrality.items()): + ... print(f"{n} {c:.2f}") + 0 0.37 + 1 0.60 + 2 0.60 + 3 0.37 + + See Also + -------- + katz_centrality_numpy + eigenvector_centrality + eigenvector_centrality_numpy + :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank` + :func:`~networkx.algorithms.link_analysis.hits_alg.hits` + + Notes + ----- + Katz centrality was introduced by [2]_. + + This algorithm it uses the power method to find the eigenvector + corresponding to the largest eigenvalue of the adjacency matrix of ``G``. + The parameter ``alpha`` should be strictly less than the inverse of largest + eigenvalue of the adjacency matrix for the algorithm to converge. + You can use ``max(nx.adjacency_spectrum(G))`` to get $\lambda_{\max}$ the largest + eigenvalue of the adjacency matrix. + The iteration will stop after ``max_iter`` iterations or an error tolerance of + ``number_of_nodes(G) * tol`` has been reached. + + For strongly connected graphs, as $\alpha \to 1/\lambda_{\max}$, and $\beta > 0$, + Katz centrality approaches the results for eigenvector centrality. + + For directed graphs this finds "left" eigenvectors which corresponds + to the in-edges in the graph. For out-edges Katz centrality, + first reverse the graph with ``G.reverse()``. + + References + ---------- + .. [1] Mark E. J. Newman: + Networks: An Introduction. + Oxford University Press, USA, 2010, p. 720. + .. [2] Leo Katz: + A New Status Index Derived from Sociometric Index. + Psychometrika 18(1):39–43, 1953 + https://link.springer.com/content/pdf/10.1007/BF02289026.pdf + """ + if len(G) == 0: + return {} + + nnodes = G.number_of_nodes() + + if nstart is None: + # choose starting vector with entries of 0 + x = {n: 0 for n in G} + else: + x = nstart + + try: + b = dict.fromkeys(G, float(beta)) + except (TypeError, ValueError, AttributeError) as err: + b = beta + if set(beta) != set(G): + raise nx.NetworkXError( + "beta dictionary must have a value for every node" + ) from err + + # make up to max_iter iterations + for _ in range(max_iter): + xlast = x + x = dict.fromkeys(xlast, 0) + # do the multiplication y^T = Alpha * x^T A + Beta + for n in x: + for nbr in G[n]: + x[nbr] += xlast[n] * G[n][nbr].get(weight, 1) + for n in x: + x[n] = alpha * x[n] + b[n] + + # check convergence + error = sum(abs(x[n] - xlast[n]) for n in x) + if error < nnodes * tol: + if normalized: + # normalize vector + try: + s = 1.0 / math.hypot(*x.values()) + except ZeroDivisionError: + s = 1.0 + else: + s = 1 + for n in x: + x[n] *= s + return x + raise nx.PowerIterationFailedConvergence(max_iter) + + +@not_implemented_for("multigraph") +@nx._dispatchable(edge_attrs="weight") +def katz_centrality_numpy(G, alpha=0.1, beta=1.0, normalized=True, weight=None): + r"""Compute the Katz centrality for the graph G. + + Katz centrality computes the centrality for a node based on the centrality + of its neighbors. It is a generalization of the eigenvector centrality. The + Katz centrality for node $i$ is + + .. math:: + + x_i = \alpha \sum_{j} A_{ij} x_j + \beta, + + where $A$ is the adjacency matrix of graph G with eigenvalues $\lambda$. + + The parameter $\beta$ controls the initial centrality and + + .. math:: + + \alpha < \frac{1}{\lambda_{\max}}. + + Katz centrality computes the relative influence of a node within a + network by measuring the number of the immediate neighbors (first + degree nodes) and also all other nodes in the network that connect + to the node under consideration through these immediate neighbors. + + Extra weight can be provided to immediate neighbors through the + parameter $\beta$. Connections made with distant neighbors + are, however, penalized by an attenuation factor $\alpha$ which + should be strictly less than the inverse largest eigenvalue of the + adjacency matrix in order for the Katz centrality to be computed + correctly. More information is provided in [1]_. + + Parameters + ---------- + G : graph + A NetworkX graph + + alpha : float + Attenuation factor + + beta : scalar or dictionary, optional (default=1.0) + Weight attributed to the immediate neighborhood. If not a scalar the + dictionary must have an value for every node. + + normalized : bool + If True normalize the resulting values. + + weight : None or string, optional + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + In this measure the weight is interpreted as the connection strength. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with Katz centrality as the value. + + Raises + ------ + NetworkXError + If the parameter `beta` is not a scalar but lacks a value for at least + one node + + Examples + -------- + >>> import math + >>> G = nx.path_graph(4) + >>> phi = (1 + math.sqrt(5)) / 2.0 # largest eigenvalue of adj matrix + >>> centrality = nx.katz_centrality_numpy(G, 1 / phi) + >>> for n, c in sorted(centrality.items()): + ... print(f"{n} {c:.2f}") + 0 0.37 + 1 0.60 + 2 0.60 + 3 0.37 + + See Also + -------- + katz_centrality + eigenvector_centrality_numpy + eigenvector_centrality + :func:`~networkx.algorithms.link_analysis.pagerank_alg.pagerank` + :func:`~networkx.algorithms.link_analysis.hits_alg.hits` + + Notes + ----- + Katz centrality was introduced by [2]_. + + This algorithm uses a direct linear solver to solve the above equation. + The parameter ``alpha`` should be strictly less than the inverse of largest + eigenvalue of the adjacency matrix for there to be a solution. + You can use ``max(nx.adjacency_spectrum(G))`` to get $\lambda_{\max}$ the largest + eigenvalue of the adjacency matrix. + + For strongly connected graphs, as $\alpha \to 1/\lambda_{\max}$, and $\beta > 0$, + Katz centrality approaches the results for eigenvector centrality. + + For directed graphs this finds "left" eigenvectors which corresponds + to the in-edges in the graph. For out-edges Katz centrality, + first reverse the graph with ``G.reverse()``. + + References + ---------- + .. [1] Mark E. J. Newman: + Networks: An Introduction. + Oxford University Press, USA, 2010, p. 173. + .. [2] Leo Katz: + A New Status Index Derived from Sociometric Index. + Psychometrika 18(1):39–43, 1953 + https://link.springer.com/content/pdf/10.1007/BF02289026.pdf + """ + import numpy as np + + if len(G) == 0: + return {} + try: + nodelist = beta.keys() + if set(nodelist) != set(G): + raise nx.NetworkXError("beta dictionary must have a value for every node") + b = np.array(list(beta.values()), dtype=float) + except AttributeError: + nodelist = list(G) + try: + b = np.ones((len(nodelist), 1)) * beta + except (TypeError, ValueError, AttributeError) as err: + raise nx.NetworkXError("beta must be a number") from err + + A = nx.adjacency_matrix(G, nodelist=nodelist, weight=weight).todense().T + n = A.shape[0] + centrality = np.linalg.solve(np.eye(n, n) - (alpha * A), b).squeeze() + + # Normalize: rely on truediv to cast to float, then tolist to make Python numbers + norm = np.sign(sum(centrality)) * np.linalg.norm(centrality) if normalized else 1 + return dict(zip(nodelist, (centrality / norm).tolist())) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/laplacian.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/laplacian.py new file mode 100644 index 0000000000000000000000000000000000000000..4bd4d5983faad8bca5d59594ec2d45ffcdba0c81 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/laplacian.py @@ -0,0 +1,150 @@ +""" +Laplacian centrality measures. +""" + +import networkx as nx + +__all__ = ["laplacian_centrality"] + + +@nx._dispatchable(edge_attrs="weight") +def laplacian_centrality( + G, normalized=True, nodelist=None, weight="weight", walk_type=None, alpha=0.95 +): + r"""Compute the Laplacian centrality for nodes in the graph `G`. + + The Laplacian Centrality of a node ``i`` is measured by the drop in the + Laplacian Energy after deleting node ``i`` from the graph. The Laplacian Energy + is the sum of the squared eigenvalues of a graph's Laplacian matrix. + + .. math:: + + C_L(u_i,G) = \frac{(\Delta E)_i}{E_L (G)} = \frac{E_L (G)-E_L (G_i)}{E_L (G)} + + E_L (G) = \sum_{i=0}^n \lambda_i^2 + + Where $E_L (G)$ is the Laplacian energy of graph `G`, + E_L (G_i) is the Laplacian energy of graph `G` after deleting node ``i`` + and $\lambda_i$ are the eigenvalues of `G`'s Laplacian matrix. + This formula shows the normalized value. Without normalization, + the numerator on the right side is returned. + + Parameters + ---------- + G : graph + A networkx graph + + normalized : bool (default = True) + If True the centrality score is scaled so the sum over all nodes is 1. + If False the centrality score for each node is the drop in Laplacian + energy when that node is removed. + + nodelist : list, optional (default = None) + The rows and columns are ordered according to the nodes in nodelist. + If nodelist is None, then the ordering is produced by G.nodes(). + + weight: string or None, optional (default=`weight`) + Optional parameter `weight` to compute the Laplacian matrix. + The edge data key used to compute each value in the matrix. + If None, then each edge has weight 1. + + walk_type : string or None, optional (default=None) + Optional parameter `walk_type` used when calling + :func:`directed_laplacian_matrix `. + One of ``"random"``, ``"lazy"``, or ``"pagerank"``. If ``walk_type=None`` + (the default), then a value is selected according to the properties of `G`: + - ``walk_type="random"`` if `G` is strongly connected and aperiodic + - ``walk_type="lazy"`` if `G` is strongly connected but not aperiodic + - ``walk_type="pagerank"`` for all other cases. + + alpha : real (default = 0.95) + Optional parameter `alpha` used when calling + :func:`directed_laplacian_matrix `. + (1 - alpha) is the teleportation probability used with pagerank. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with Laplacian centrality as the value. + + Examples + -------- + >>> G = nx.Graph() + >>> edges = [(0, 1, 4), (0, 2, 2), (2, 1, 1), (1, 3, 2), (1, 4, 2), (4, 5, 1)] + >>> G.add_weighted_edges_from(edges) + >>> sorted((v, f"{c:0.2f}") for v, c in laplacian_centrality(G).items()) + [(0, '0.70'), (1, '0.90'), (2, '0.28'), (3, '0.22'), (4, '0.26'), (5, '0.04')] + + Notes + ----- + The algorithm is implemented based on [1]_ with an extension to directed graphs + using the ``directed_laplacian_matrix`` function. + + Raises + ------ + NetworkXPointlessConcept + If the graph `G` is the null graph. + ZeroDivisionError + If the graph `G` has no edges (is empty) and normalization is requested. + + References + ---------- + .. [1] Qi, X., Fuller, E., Wu, Q., Wu, Y., and Zhang, C.-Q. (2012). + Laplacian centrality: A new centrality measure for weighted networks. + Information Sciences, 194:240-253. + https://math.wvu.edu/~cqzhang/Publication-files/my-paper/INS-2012-Laplacian-W.pdf + + See Also + -------- + :func:`~networkx.linalg.laplacianmatrix.directed_laplacian_matrix` + :func:`~networkx.linalg.laplacianmatrix.laplacian_matrix` + """ + import numpy as np + import scipy as sp + + if len(G) == 0: + raise nx.NetworkXPointlessConcept("null graph has no centrality defined") + if G.size(weight=weight) == 0: + if normalized: + raise ZeroDivisionError("graph with no edges has zero full energy") + return {n: 0 for n in G} + + if nodelist is not None: + nodeset = set(G.nbunch_iter(nodelist)) + if len(nodeset) != len(nodelist): + raise nx.NetworkXError("nodelist has duplicate nodes or nodes not in G") + nodes = nodelist + [n for n in G if n not in nodeset] + else: + nodelist = nodes = list(G) + + if G.is_directed(): + lap_matrix = nx.directed_laplacian_matrix(G, nodes, weight, walk_type, alpha) + else: + lap_matrix = nx.laplacian_matrix(G, nodes, weight).toarray() + + full_energy = np.sum(lap_matrix**2) + + # calculate laplacian centrality + laplace_centralities_dict = {} + for i, node in enumerate(nodelist): + # remove row and col i from lap_matrix + all_but_i = list(np.arange(lap_matrix.shape[0])) + all_but_i.remove(i) + A_2 = lap_matrix[all_but_i, :][:, all_but_i] + + # Adjust diagonal for removed row + new_diag = lap_matrix.diagonal() - abs(lap_matrix[:, i]) + np.fill_diagonal(A_2, new_diag[all_but_i]) + + if len(all_but_i) > 0: # catches degenerate case of single node + new_energy = np.sum(A_2**2) + else: + new_energy = 0.0 + + lapl_cent = full_energy - new_energy + if normalized: + lapl_cent = lapl_cent / full_energy + + laplace_centralities_dict[node] = float(lapl_cent) + + return laplace_centralities_dict diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/load.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/load.py new file mode 100644 index 0000000000000000000000000000000000000000..fc46edd6fa2a1555181058aa17c68cf8a9820429 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/load.py @@ -0,0 +1,200 @@ +"""Load centrality.""" + +from operator import itemgetter + +import networkx as nx + +__all__ = ["load_centrality", "edge_load_centrality"] + + +@nx._dispatchable(edge_attrs="weight") +def newman_betweenness_centrality(G, v=None, cutoff=None, normalized=True, weight=None): + """Compute load centrality for nodes. + + The load centrality of a node is the fraction of all shortest + paths that pass through that node. + + Parameters + ---------- + G : graph + A networkx graph. + + normalized : bool, optional (default=True) + If True the betweenness values are normalized by b=b/(n-1)(n-2) where + n is the number of nodes in G. + + weight : None or string, optional (default=None) + If None, edge weights are ignored. + Otherwise holds the name of the edge attribute used as weight. + The weight of an edge is treated as the length or distance between the two sides. + + cutoff : bool, optional (default=None) + If specified, only consider paths of length <= cutoff. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with centrality as the value. + + See Also + -------- + betweenness_centrality + + Notes + ----- + Load centrality is slightly different than betweenness. It was originally + introduced by [2]_. For this load algorithm see [1]_. + + References + ---------- + .. [1] Mark E. J. Newman: + Scientific collaboration networks. II. + Shortest paths, weighted networks, and centrality. + Physical Review E 64, 016132, 2001. + http://journals.aps.org/pre/abstract/10.1103/PhysRevE.64.016132 + .. [2] Kwang-Il Goh, Byungnam Kahng and Doochul Kim + Universal behavior of Load Distribution in Scale-Free Networks. + Physical Review Letters 87(27):1–4, 2001. + https://doi.org/10.1103/PhysRevLett.87.278701 + """ + if v is not None: # only one node + betweenness = 0.0 + for source in G: + ubetween = _node_betweenness(G, source, cutoff, False, weight) + betweenness += ubetween[v] if v in ubetween else 0 + if normalized: + order = G.order() + if order <= 2: + return betweenness # no normalization b=0 for all nodes + betweenness *= 1.0 / ((order - 1) * (order - 2)) + else: + betweenness = {}.fromkeys(G, 0.0) + for source in betweenness: + ubetween = _node_betweenness(G, source, cutoff, False, weight) + for vk in ubetween: + betweenness[vk] += ubetween[vk] + if normalized: + order = G.order() + if order <= 2: + return betweenness # no normalization b=0 for all nodes + scale = 1.0 / ((order - 1) * (order - 2)) + for v in betweenness: + betweenness[v] *= scale + return betweenness # all nodes + + +def _node_betweenness(G, source, cutoff=False, normalized=True, weight=None): + """Node betweenness_centrality helper: + + See betweenness_centrality for what you probably want. + This actually computes "load" and not betweenness. + See https://networkx.lanl.gov/ticket/103 + + This calculates the load of each node for paths from a single source. + (The fraction of number of shortests paths from source that go + through each node.) + + To get the load for a node you need to do all-pairs shortest paths. + + If weight is not None then use Dijkstra for finding shortest paths. + """ + # get the predecessor and path length data + if weight is None: + (pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True) + else: + (pred, length) = nx.dijkstra_predecessor_and_distance(G, source, cutoff, weight) + + # order the nodes by path length + onodes = [(l, vert) for (vert, l) in length.items()] + onodes.sort() + onodes[:] = [vert for (l, vert) in onodes if l > 0] + + # initialize betweenness + between = {}.fromkeys(length, 1.0) + + while onodes: + v = onodes.pop() + if v in pred: + num_paths = len(pred[v]) # Discount betweenness if more than + for x in pred[v]: # one shortest path. + if x == source: # stop if hit source because all remaining v + break # also have pred[v]==[source] + between[x] += between[v] / num_paths + # remove source + for v in between: + between[v] -= 1 + # rescale to be between 0 and 1 + if normalized: + l = len(between) + if l > 2: + # scale by 1/the number of possible paths + scale = 1 / ((l - 1) * (l - 2)) + for v in between: + between[v] *= scale + return between + + +load_centrality = newman_betweenness_centrality + + +@nx._dispatchable +def edge_load_centrality(G, cutoff=False): + """Compute edge load. + + WARNING: This concept of edge load has not been analysed + or discussed outside of NetworkX that we know of. + It is based loosely on load_centrality in the sense that + it counts the number of shortest paths which cross each edge. + This function is for demonstration and testing purposes. + + Parameters + ---------- + G : graph + A networkx graph + + cutoff : bool, optional (default=False) + If specified, only consider paths of length <= cutoff. + + Returns + ------- + A dict keyed by edge 2-tuple to the number of shortest paths + which use that edge. Where more than one path is shortest + the count is divided equally among paths. + """ + betweenness = {} + for u, v in G.edges(): + betweenness[(u, v)] = 0.0 + betweenness[(v, u)] = 0.0 + + for source in G: + ubetween = _edge_betweenness(G, source, cutoff=cutoff) + for e, ubetweenv in ubetween.items(): + betweenness[e] += ubetweenv # cumulative total + return betweenness + + +def _edge_betweenness(G, source, nodes=None, cutoff=False): + """Edge betweenness helper.""" + # get the predecessor data + (pred, length) = nx.predecessor(G, source, cutoff=cutoff, return_seen=True) + # order the nodes by path length + onodes = [n for n, d in sorted(length.items(), key=itemgetter(1))] + # initialize betweenness, doesn't account for any edge weights + between = {} + for u, v in G.edges(nodes): + between[(u, v)] = 1.0 + between[(v, u)] = 1.0 + + while onodes: # work through all paths + v = onodes.pop() + if v in pred: + # Discount betweenness if more than one shortest path. + num_paths = len(pred[v]) + for w in pred[v]: + if w in pred: + # Discount betweenness, mult path + num_paths = len(pred[w]) + for x in pred[w]: + between[(w, x)] += between[(v, w)] / num_paths + between[(x, w)] += between[(w, v)] / num_paths + return between diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/percolation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/percolation.py new file mode 100644 index 0000000000000000000000000000000000000000..0d4c87132b48fe02f6a86e06f4ada0d7a72239f1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/percolation.py @@ -0,0 +1,128 @@ +"""Percolation centrality measures.""" + +import networkx as nx +from networkx.algorithms.centrality.betweenness import ( + _single_source_dijkstra_path_basic as dijkstra, +) +from networkx.algorithms.centrality.betweenness import ( + _single_source_shortest_path_basic as shortest_path, +) + +__all__ = ["percolation_centrality"] + + +@nx._dispatchable(node_attrs="attribute", edge_attrs="weight") +def percolation_centrality(G, attribute="percolation", states=None, weight=None): + r"""Compute the percolation centrality for nodes. + + Percolation centrality of a node $v$, at a given time, is defined + as the proportion of ‘percolated paths’ that go through that node. + + This measure quantifies relative impact of nodes based on their + topological connectivity, as well as their percolation states. + + Percolation states of nodes are used to depict network percolation + scenarios (such as during infection transmission in a social network + of individuals, spreading of computer viruses on computer networks, or + transmission of disease over a network of towns) over time. In this + measure usually the percolation state is expressed as a decimal + between 0.0 and 1.0. + + When all nodes are in the same percolated state this measure is + equivalent to betweenness centrality. + + Parameters + ---------- + G : graph + A NetworkX graph. + + attribute : None or string, optional (default='percolation') + Name of the node attribute to use for percolation state, used + if `states` is None. If a node does not set the attribute the + state of that node will be set to the default value of 1. + If all nodes do not have the attribute all nodes will be set to + 1 and the centrality measure will be equivalent to betweenness centrality. + + states : None or dict, optional (default=None) + Specify percolation states for the nodes, nodes as keys states + as values. + + weight : None or string, optional (default=None) + If None, all edge weights are considered equal. + Otherwise holds the name of the edge attribute used as weight. + The weight of an edge is treated as the length or distance between the two sides. + + + Returns + ------- + nodes : dictionary + Dictionary of nodes with percolation centrality as the value. + + See Also + -------- + betweenness_centrality + + Notes + ----- + The algorithm is from Mahendra Piraveenan, Mikhail Prokopenko, and + Liaquat Hossain [1]_ + Pair dependencies are calculated and accumulated using [2]_ + + For weighted graphs the edge weights must be greater than zero. + Zero edge weights can produce an infinite number of equal length + paths between pairs of nodes. + + References + ---------- + .. [1] Mahendra Piraveenan, Mikhail Prokopenko, Liaquat Hossain + Percolation Centrality: Quantifying Graph-Theoretic Impact of Nodes + during Percolation in Networks + http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0053095 + .. [2] Ulrik Brandes: + A Faster Algorithm for Betweenness Centrality. + Journal of Mathematical Sociology 25(2):163-177, 2001. + https://doi.org/10.1080/0022250X.2001.9990249 + """ + percolation = dict.fromkeys(G, 0.0) # b[v]=0 for v in G + + nodes = G + + if states is None: + states = nx.get_node_attributes(nodes, attribute, default=1) + + # sum of all percolation states + p_sigma_x_t = 0.0 + for v in states.values(): + p_sigma_x_t += v + + for s in nodes: + # single source shortest paths + if weight is None: # use BFS + S, P, sigma, _ = shortest_path(G, s) + else: # use Dijkstra's algorithm + S, P, sigma, _ = dijkstra(G, s, weight) + # accumulation + percolation = _accumulate_percolation( + percolation, S, P, sigma, s, states, p_sigma_x_t + ) + + n = len(G) + + for v in percolation: + percolation[v] *= 1 / (n - 2) + + return percolation + + +def _accumulate_percolation(percolation, S, P, sigma, s, states, p_sigma_x_t): + delta = dict.fromkeys(S, 0) + while S: + w = S.pop() + coeff = (1 + delta[w]) / sigma[w] + for v in P[w]: + delta[v] += sigma[v] * coeff + if w != s: + # percolation weight + pw_s_w = states[s] / (p_sigma_x_t - states[w]) + percolation[w] += delta[w] * pw_s_w + return percolation diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py new file mode 100644 index 0000000000000000000000000000000000000000..23018af0b1eeaca421d2f56ac48511c673ecf604 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/reaching.py @@ -0,0 +1,209 @@ +"""Functions for computing reaching centrality of a node or a graph.""" + +import networkx as nx +from networkx.utils import pairwise + +__all__ = ["global_reaching_centrality", "local_reaching_centrality"] + + +def _average_weight(G, path, weight=None): + """Returns the average weight of an edge in a weighted path. + + Parameters + ---------- + G : graph + A networkx graph. + + path: list + A list of vertices that define the path. + + weight : None or string, optional (default=None) + If None, edge weights are ignored. Then the average weight of an edge + is assumed to be the multiplicative inverse of the length of the path. + Otherwise holds the name of the edge attribute used as weight. + """ + path_length = len(path) - 1 + if path_length <= 0: + return 0 + if weight is None: + return 1 / path_length + total_weight = sum(G.edges[i, j][weight] for i, j in pairwise(path)) + return total_weight / path_length + + +@nx._dispatchable(edge_attrs="weight") +def global_reaching_centrality(G, weight=None, normalized=True): + """Returns the global reaching centrality of a directed graph. + + The *global reaching centrality* of a weighted directed graph is the + average over all nodes of the difference between the local reaching + centrality of the node and the greatest local reaching centrality of + any node in the graph [1]_. For more information on the local + reaching centrality, see :func:`local_reaching_centrality`. + Informally, the local reaching centrality is the proportion of the + graph that is reachable from the neighbors of the node. + + Parameters + ---------- + G : DiGraph + A networkx DiGraph. + + weight : None or string, optional (default=None) + Attribute to use for edge weights. If ``None``, each edge weight + is assumed to be one. A higher weight implies a stronger + connection between nodes and a *shorter* path length. + + normalized : bool, optional (default=True) + Whether to normalize the edge weights by the total sum of edge + weights. + + Returns + ------- + h : float + The global reaching centrality of the graph. + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_edge(1, 2) + >>> G.add_edge(1, 3) + >>> nx.global_reaching_centrality(G) + 1.0 + >>> G.add_edge(3, 2) + >>> nx.global_reaching_centrality(G) + 0.75 + + See also + -------- + local_reaching_centrality + + References + ---------- + .. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek. + "Hierarchy Measure for Complex Networks." + *PLoS ONE* 7.3 (2012): e33799. + https://doi.org/10.1371/journal.pone.0033799 + """ + if nx.is_negatively_weighted(G, weight=weight): + raise nx.NetworkXError("edge weights must be positive") + total_weight = G.size(weight=weight) + if total_weight <= 0: + raise nx.NetworkXError("Size of G must be positive") + # If provided, weights must be interpreted as connection strength + # (so higher weights are more likely to be chosen). However, the + # shortest path algorithms in NetworkX assume the provided "weight" + # is actually a distance (so edges with higher weight are less + # likely to be chosen). Therefore we need to invert the weights when + # computing shortest paths. + # + # If weight is None, we leave it as-is so that the shortest path + # algorithm can use a faster, unweighted algorithm. + if weight is not None: + + def as_distance(u, v, d): + return total_weight / d.get(weight, 1) + + shortest_paths = dict(nx.shortest_path(G, weight=as_distance)) + else: + shortest_paths = dict(nx.shortest_path(G)) + + centrality = local_reaching_centrality + # TODO This can be trivially parallelized. + lrc = [ + centrality(G, node, paths=paths, weight=weight, normalized=normalized) + for node, paths in shortest_paths.items() + ] + + max_lrc = max(lrc) + return sum(max_lrc - c for c in lrc) / (len(G) - 1) + + +@nx._dispatchable(edge_attrs="weight") +def local_reaching_centrality(G, v, paths=None, weight=None, normalized=True): + """Returns the local reaching centrality of a node in a directed + graph. + + The *local reaching centrality* of a node in a directed graph is the + proportion of other nodes reachable from that node [1]_. + + Parameters + ---------- + G : DiGraph + A NetworkX DiGraph. + + v : node + A node in the directed graph `G`. + + paths : dictionary (default=None) + If this is not `None` it must be a dictionary representation + of single-source shortest paths, as computed by, for example, + :func:`networkx.shortest_path` with source node `v`. Use this + keyword argument if you intend to invoke this function many + times but don't want the paths to be recomputed each time. + + weight : None or string, optional (default=None) + Attribute to use for edge weights. If `None`, each edge weight + is assumed to be one. A higher weight implies a stronger + connection between nodes and a *shorter* path length. + + normalized : bool, optional (default=True) + Whether to normalize the edge weights by the total sum of edge + weights. + + Returns + ------- + h : float + The local reaching centrality of the node ``v`` in the graph + ``G``. + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_edges_from([(1, 2), (1, 3)]) + >>> nx.local_reaching_centrality(G, 3) + 0.0 + >>> G.add_edge(3, 2) + >>> nx.local_reaching_centrality(G, 3) + 0.5 + + See also + -------- + global_reaching_centrality + + References + ---------- + .. [1] Mones, Enys, Lilla Vicsek, and Tamás Vicsek. + "Hierarchy Measure for Complex Networks." + *PLoS ONE* 7.3 (2012): e33799. + https://doi.org/10.1371/journal.pone.0033799 + """ + # Corner case: graph with single node containing a self-loop + if (total_weight := G.size(weight=weight)) > 0 and len(G) == 1: + raise nx.NetworkXError( + "local_reaching_centrality of a single node with self-loop not well-defined" + ) + if paths is None: + if nx.is_negatively_weighted(G, weight=weight): + raise nx.NetworkXError("edge weights must be positive") + if total_weight <= 0: + raise nx.NetworkXError("Size of G must be positive") + if weight is not None: + # Interpret weights as lengths. + def as_distance(u, v, d): + return total_weight / d.get(weight, 1) + + paths = nx.shortest_path(G, source=v, weight=as_distance) + else: + paths = nx.shortest_path(G, source=v) + # If the graph is unweighted, simply return the proportion of nodes + # reachable from the source node ``v``. + if weight is None and G.is_directed(): + return (len(paths) - 1) / (len(G) - 1) + if normalized and weight is not None: + norm = G.size(weight=weight) / G.size() + else: + norm = 1 + # TODO This can be trivially parallelized. + avgw = (_average_weight(G, path, weight=weight) for path in paths.values()) + sum_avg_weight = sum(avgw) / norm + return sum_avg_weight / (len(G) - 1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/second_order.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/second_order.py new file mode 100644 index 0000000000000000000000000000000000000000..35583cd63e55d14c0c389040cbdeab39b27d1bf9 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/second_order.py @@ -0,0 +1,141 @@ +"""Copyright (c) 2015 – Thomson Licensing, SAS + +Redistribution and use in source and binary forms, with or without +modification, are permitted (subject to the limitations in the +disclaimer below) provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +* Neither the name of Thomson Licensing, or Technicolor, nor the names +of its contributors may be used to endorse or promote products derived +from this software without specific prior written permission. + +NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE +GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT +HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import networkx as nx +from networkx.utils import not_implemented_for + +# Authors: Erwan Le Merrer (erwan.lemerrer@technicolor.com) + +__all__ = ["second_order_centrality"] + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight") +def second_order_centrality(G, weight="weight"): + """Compute the second order centrality for nodes of G. + + The second order centrality of a given node is the standard deviation of + the return times to that node of a perpetual random walk on G: + + Parameters + ---------- + G : graph + A NetworkX connected and undirected graph. + + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + + Returns + ------- + nodes : dictionary + Dictionary keyed by node with second order centrality as the value. + + Examples + -------- + >>> G = nx.star_graph(10) + >>> soc = nx.second_order_centrality(G) + >>> print(sorted(soc.items(), key=lambda x: x[1])[0][0]) # pick first id + 0 + + Raises + ------ + NetworkXException + If the graph G is empty, non connected or has negative weights. + + See Also + -------- + betweenness_centrality + + Notes + ----- + Lower values of second order centrality indicate higher centrality. + + The algorithm is from Kermarrec, Le Merrer, Sericola and Trédan [1]_. + + This code implements the analytical version of the algorithm, i.e., + there is no simulation of a random walk process involved. The random walk + is here unbiased (corresponding to eq 6 of the paper [1]_), thus the + centrality values are the standard deviations for random walk return times + on the transformed input graph G (equal in-degree at each nodes by adding + self-loops). + + Complexity of this implementation, made to run locally on a single machine, + is O(n^3), with n the size of G, which makes it viable only for small + graphs. + + References + ---------- + .. [1] Anne-Marie Kermarrec, Erwan Le Merrer, Bruno Sericola, Gilles Trédan + "Second order centrality: Distributed assessment of nodes criticity in + complex networks", Elsevier Computer Communications 34(5):619-628, 2011. + """ + import numpy as np + + n = len(G) + + if n == 0: + raise nx.NetworkXException("Empty graph.") + if not nx.is_connected(G): + raise nx.NetworkXException("Non connected graph.") + if any(d.get(weight, 0) < 0 for u, v, d in G.edges(data=True)): + raise nx.NetworkXException("Graph has negative edge weights.") + + # balancing G for Metropolis-Hastings random walks + G = nx.DiGraph(G) + in_deg = dict(G.in_degree(weight=weight)) + d_max = max(in_deg.values()) + for i, deg in in_deg.items(): + if deg < d_max: + G.add_edge(i, i, weight=d_max - deg) + + P = nx.to_numpy_array(G) + P /= P.sum(axis=1)[:, np.newaxis] # to transition probability matrix + + def _Qj(P, j): + P = P.copy() + P[:, j] = 0 + return P + + M = np.empty([n, n]) + + for i in range(n): + M[:, i] = np.linalg.solve( + np.identity(n) - _Qj(P, i), np.ones([n, 1])[:, 0] + ) # eq 3 + + return dict( + zip( + G.nodes, + (float(np.sqrt(2 * np.sum(M[:, i]) - n * (n + 1))) for i in range(n)), + ) + ) # eq 6 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/subgraph_alg.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/subgraph_alg.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd67445e5d363969fd29e6712f1041babae9865 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/subgraph_alg.py @@ -0,0 +1,361 @@ +""" +Subraph centrality and communicability betweenness. +""" + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = [ + "subgraph_centrality_exp", + "subgraph_centrality", + "communicability_betweenness_centrality", + "estrada_index", +] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def subgraph_centrality_exp(G, *, normalized=False): + r"""Returns the subgraph centrality for each node of G. + + Subgraph centrality of a node `n` is the sum of weighted closed + walks of all lengths starting and ending at node `n`. The weights + decrease with path length. Each closed walk is associated with a + connected subgraph ([1]_). + + Parameters + ---------- + G: graph + normalized : bool + If True, normalize the centrality values using the largest eigenvalue of the + adjacency matrix so that the centrality values are generally between 0 and 1. + + Returns + ------- + nodes:dictionary + Dictionary of nodes with subgraph centrality as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + subgraph_centrality: + Alternative algorithm of the subgraph centrality for each node of G. + + Notes + ----- + This version of the algorithm exponentiates the adjacency matrix. + + The subgraph centrality of a node `u` in G can be found using + the matrix exponential of the adjacency matrix of G [1]_, + + .. math:: + + SC(u)=(e^A)_{uu} . + + Examples + -------- + (Example from [1]_) + + >>> G = nx.Graph( + ... [ + ... (1, 2), + ... (1, 5), + ... (1, 8), + ... (2, 3), + ... (2, 8), + ... (3, 4), + ... (3, 6), + ... (4, 5), + ... (4, 7), + ... (5, 6), + ... (6, 7), + ... (7, 8), + ... ] + ... ) + >>> sc = nx.subgraph_centrality_exp(G) + >>> print([f"{node} {sc[node]:0.2f}" for node in sorted(sc)]) + ['1 3.90', '2 3.90', '3 3.64', '4 3.71', '5 3.64', '6 3.71', '7 3.64', '8 3.90'] + >>> sc = nx.subgraph_centrality(G, normalized=True) + >>> print([f"{node} {sc[node]:0.3f}" for node in sorted(sc)]) + ['1 0.194', '2 0.194', '3 0.181', '4 0.184', '5 0.181', '6 0.184', '7 0.181', '8 0.194'] + + References + ---------- + .. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez, + "Subgraph centrality in complex networks", + Physical Review E 71, 056103 (2005). + https://arxiv.org/abs/cond-mat/0504730 + + """ + # alternative implementation that calculates the matrix exponential + import scipy as sp + + nodelist = list(G) # ordering of nodes in matrix + A = nx.to_numpy_array(G, nodelist) + # convert to 0-1 matrix + A[A != 0.0] = 1 + expA = sp.linalg.expm(A) + values = map(float, expA.diagonal()) + if normalized: + values = values / values.max() + # convert diagonal to dictionary keyed by node + sc = dict(zip(nodelist, values)) + return sc + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def subgraph_centrality(G, *, normalized=False): + r"""Returns subgraph centrality for each node in G. + + Subgraph centrality of a node `n` is the sum of weighted closed + walks of all lengths starting and ending at node `n`. The weights + decrease with path length. Each closed walk is associated with a + connected subgraph ([1]_). + + Parameters + ---------- + G: Graph + normalized : bool + If True, normalize the centrality values using the largest eigenvalue of the + adjacency matrix so that the centrality values are generally between 0 and 1. + + Returns + ------- + nodes : dictionary + Dictionary of nodes with subgraph centrality as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + See Also + -------- + subgraph_centrality_exp: + Alternative algorithm of the subgraph centrality for each node of G. + + Notes + ----- + This version of the algorithm computes eigenvalues and eigenvectors + of the adjacency matrix. + + Subgraph centrality of a node `u` in G can be found using + a spectral decomposition of the adjacency matrix [1]_, + + .. math:: + + SC(u)=\sum_{j=1}^{N}(v_{j}^{u})^2 e^{\lambda_{j}}, + + where `v_j` is an eigenvector of the adjacency matrix `A` of G + corresponding to the eigenvalue `\lambda_j`. + + Examples + -------- + (Example from [1]_) + + >>> G = nx.Graph( + ... [ + ... (1, 2), + ... (1, 5), + ... (1, 8), + ... (2, 3), + ... (2, 8), + ... (3, 4), + ... (3, 6), + ... (4, 5), + ... (4, 7), + ... (5, 6), + ... (6, 7), + ... (7, 8), + ... ] + ... ) + >>> sc = nx.subgraph_centrality(G) + >>> print([f"{node} {sc[node]:0.2f}" for node in sorted(sc)]) + ['1 3.90', '2 3.90', '3 3.64', '4 3.71', '5 3.64', '6 3.71', '7 3.64', '8 3.90'] + >>> sc = nx.subgraph_centrality(G, normalized=True) + >>> print([f"{node} {sc[node]:0.3f}" for node in sorted(sc)]) + ['1 0.194', '2 0.194', '3 0.181', '4 0.184', '5 0.181', '6 0.184', '7 0.181', '8 0.194'] + + References + ---------- + .. [1] Ernesto Estrada, Juan A. Rodriguez-Velazquez, + "Subgraph centrality in complex networks", + Physical Review E 71, 056103 (2005). + https://arxiv.org/abs/cond-mat/0504730 + + """ + import numpy as np + + nodelist = list(G) # ordering of nodes in matrix + A = nx.to_numpy_array(G, nodelist) + # convert to 0-1 matrix + A[np.nonzero(A)] = 1 + w, v = np.linalg.eigh(A) + vsquare = np.array(v) ** 2 + if normalized: + expw = np.exp(w - w.max()) + else: + expw = np.exp(w) + xg = vsquare @ expw + # convert vector dictionary keyed by node + sc = dict(zip(nodelist, map(float, xg))) + return sc + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable +def communicability_betweenness_centrality(G): + r"""Returns subgraph communicability for all pairs of nodes in G. + + Communicability betweenness measure makes use of the number of walks + connecting every pair of nodes as the basis of a betweenness centrality + measure. + + Parameters + ---------- + G: graph + + Returns + ------- + nodes : dictionary + Dictionary of nodes with communicability betweenness as the value. + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + Notes + ----- + Let `G=(V,E)` be a simple undirected graph with `n` nodes and `m` edges, + and `A` denote the adjacency matrix of `G`. + + Let `G(r)=(V,E(r))` be the graph resulting from + removing all edges connected to node `r` but not the node itself. + + The adjacency matrix for `G(r)` is `A+E(r)`, where `E(r)` has nonzeros + only in row and column `r`. + + The subraph betweenness of a node `r` is [1]_ + + .. math:: + + \omega_{r} = \frac{1}{C}\sum_{p}\sum_{q}\frac{G_{prq}}{G_{pq}}, + p\neq q, q\neq r, + + where + `G_{prq}=(e^{A}_{pq} - (e^{A+E(r)})_{pq}` is the number of walks + involving node r, + `G_{pq}=(e^{A})_{pq}` is the number of closed walks starting + at node `p` and ending at node `q`, + and `C=(n-1)^{2}-(n-1)` is a normalization factor equal to the + number of terms in the sum. + + The resulting `\omega_{r}` takes values between zero and one. + The lower bound cannot be attained for a connected + graph, and the upper bound is attained in the star graph. + + References + ---------- + .. [1] Ernesto Estrada, Desmond J. Higham, Naomichi Hatano, + "Communicability Betweenness in Complex Networks" + Physica A 388 (2009) 764-774. + https://arxiv.org/abs/0905.4102 + + Examples + -------- + >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)]) + >>> cbc = nx.communicability_betweenness_centrality(G) + >>> print([f"{node} {cbc[node]:0.2f}" for node in sorted(cbc)]) + ['0 0.03', '1 0.45', '2 0.51', '3 0.45', '4 0.40', '5 0.19', '6 0.03'] + """ + import numpy as np + import scipy as sp + + nodelist = list(G) # ordering of nodes in matrix + n = len(nodelist) + A = nx.to_numpy_array(G, nodelist) + # convert to 0-1 matrix + A[np.nonzero(A)] = 1 + expA = sp.linalg.expm(A) + mapping = dict(zip(nodelist, range(n))) + cbc = {} + for v in G: + # remove row and col of node v + i = mapping[v] + row = A[i, :].copy() + col = A[:, i].copy() + A[i, :] = 0 + A[:, i] = 0 + B = (expA - sp.linalg.expm(A)) / expA + # sum with row/col of node v and diag set to zero + B[i, :] = 0 + B[:, i] = 0 + B -= np.diag(np.diag(B)) + cbc[v] = float(B.sum()) + # put row and col back + A[i, :] = row + A[:, i] = col + # rescale when more than two nodes + order = len(cbc) + if order > 2: + scale = 1.0 / ((order - 1.0) ** 2 - (order - 1.0)) + cbc = {node: value * scale for node, value in cbc.items()} + return cbc + + +@nx._dispatchable +def estrada_index(G): + r"""Returns the Estrada index of a the graph G. + + The Estrada Index is a topological index of folding or 3D "compactness" ([1]_). + + Parameters + ---------- + G: graph + + Returns + ------- + estrada index: float + + Raises + ------ + NetworkXError + If the graph is not undirected and simple. + + Notes + ----- + Let `G=(V,E)` be a simple undirected graph with `n` nodes and let + `\lambda_{1}\leq\lambda_{2}\leq\cdots\lambda_{n}` + be a non-increasing ordering of the eigenvalues of its adjacency + matrix `A`. The Estrada index is ([1]_, [2]_) + + .. math:: + EE(G)=\sum_{j=1}^n e^{\lambda _j}. + + References + ---------- + .. [1] E. Estrada, "Characterization of 3D molecular structure", + Chem. Phys. Lett. 319, 713 (2000). + https://doi.org/10.1016/S0009-2614(00)00158-5 + .. [2] José Antonio de la Peñaa, Ivan Gutman, Juan Rada, + "Estimating the Estrada index", + Linear Algebra and its Applications. 427, 1 (2007). + https://doi.org/10.1016/j.laa.2007.06.020 + + Examples + -------- + >>> G = nx.Graph([(0, 1), (1, 2), (1, 5), (5, 4), (2, 4), (2, 3), (4, 3), (3, 6)]) + >>> ei = nx.estrada_index(G) + >>> print(f"{ei:0.5}") + 20.55 + """ + return sum(subgraph_centrality(G).values()) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f7f35b71a79f481247ceb2963f87ee1a2e4685a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcb2cfe1c9885f757c56a8eb4f1ace7bbf6f60e8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality_subset.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality_subset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52cd8be308e715ef34d20d681ada19298734ae28 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_betweenness_centrality_subset.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_closeness_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_closeness_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3f819c7a5a68b65637732b5064fb7490db07c7c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_closeness_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb4e247ad5421e68560a5fd49eb8067f84823043 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81b3ff4e0861eed55d44ac4c3dee79e6fb5ffc13 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_betweenness_centrality_subset.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d20601b92b474a82406c629440d96c235fb5dec4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_current_flow_closeness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_degree_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_degree_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b810aa68b599a440e24ec6a9bfcc454ce4b987b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_degree_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_dispersion.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_dispersion.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9b84e25c40804ca1771e132a21ff229b516676e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_dispersion.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_eigenvector_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_eigenvector_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99998dcb89d2afb6ac16f08a847da9bc297af27b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_eigenvector_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a502c4d05f79ce67e82e628b4150cbc039ea4956 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_group.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_harmonic_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_harmonic_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8db9ca42ef85539c3a4654f137171aed36f9f0cd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_harmonic_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_katz_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_katz_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..851808c623a21815357f19b11ff454a7e8adf05c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_katz_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_laplacian_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_laplacian_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32b2f5438d1f573467a74b0b0214728e4effc5bb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_laplacian_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b01d2b92d858005c154f61fae071cf8c5171fc9f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_load_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_percolation_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_percolation_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd19ab42991eeee8f60898c56f7740202a4366e4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_percolation_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_reaching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_reaching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eda19999b5e24d67520ccd938f6e499ec6036a6f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_reaching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_second_order_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_second_order_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57993e8c4ecda67262def6021ea5b3775ed1e1af Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_second_order_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_subgraph.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_subgraph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a3bef92bf16286bd02dca8d54cc6a2c6fb28bbf Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_subgraph.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_trophic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_trophic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..124041292bab8d200f65abb489329f3cd1541cac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_trophic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9ecbb1aee1d12058cb37f663b798b9a98bb27c6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/__pycache__/test_voterank.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..eff14c4696061b7f9213330be5cc7e6e4399d47c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality.py @@ -0,0 +1,923 @@ +import math + +import pytest + +import networkx as nx + + +def weighted_G(): + G = nx.Graph() + G.add_edge(0, 1, weight=3) + G.add_edge(0, 2, weight=2) + G.add_edge(0, 3, weight=6) + G.add_edge(0, 4, weight=4) + G.add_edge(1, 3, weight=5) + G.add_edge(1, 5, weight=5) + G.add_edge(2, 4, weight=1) + G.add_edge(3, 4, weight=2) + G.add_edge(3, 5, weight=1) + G.add_edge(4, 5, weight=4) + return G + + +class TestBetweennessCentrality: + def test_K5(self): + """Betweenness centrality: K5""" + G = nx.complete_graph(5) + b = nx.betweenness_centrality(G, weight=None, normalized=False) + b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_K5_endpoints(self): + """Betweenness centrality: K5 endpoints""" + G = nx.complete_graph(5) + b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True) + b_answer = {0: 4.0, 1: 4.0, 2: 4.0, 3: 4.0, 4: 4.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + # normalized = True case + b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True) + b_answer = {0: 0.4, 1: 0.4, 2: 0.4, 3: 0.4, 4: 0.4} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P3_normalized(self): + """Betweenness centrality: P3 normalized""" + G = nx.path_graph(3) + b = nx.betweenness_centrality(G, weight=None, normalized=True) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P3(self): + """Betweenness centrality: P3""" + G = nx.path_graph(3) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + b = nx.betweenness_centrality(G, weight=None, normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_sample_from_P3(self): + """Betweenness centrality: P3 sample""" + G = nx.path_graph(3) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + b = nx.betweenness_centrality(G, k=3, weight=None, normalized=False, seed=1) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + b = nx.betweenness_centrality(G, k=2, weight=None, normalized=False, seed=1) + # python versions give different results with same seed + b_approx1 = {0: 0.0, 1: 1.0, 2: 0.0} + b_approx2 = {0: 0.0, 1: 0.5, 2: 0.0} + for n in sorted(G): + assert b[n] in (b_approx1[n], b_approx2[n]) + + def test_P3_endpoints(self): + """Betweenness centrality: P3 endpoints""" + G = nx.path_graph(3) + b_answer = {0: 2.0, 1: 3.0, 2: 2.0} + b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + # normalized = True case + b_answer = {0: 2 / 3, 1: 1.0, 2: 2 / 3} + b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_krackhardt_kite_graph(self): + """Betweenness centrality: Krackhardt kite graph""" + G = nx.krackhardt_kite_graph() + b_answer = { + 0: 1.667, + 1: 1.667, + 2: 0.000, + 3: 7.333, + 4: 0.000, + 5: 16.667, + 6: 16.667, + 7: 28.000, + 8: 16.000, + 9: 0.000, + } + for b in b_answer: + b_answer[b] /= 2 + b = nx.betweenness_centrality(G, weight=None, normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_krackhardt_kite_graph_normalized(self): + """Betweenness centrality: Krackhardt kite graph normalized""" + G = nx.krackhardt_kite_graph() + b_answer = { + 0: 0.023, + 1: 0.023, + 2: 0.000, + 3: 0.102, + 4: 0.000, + 5: 0.231, + 6: 0.231, + 7: 0.389, + 8: 0.222, + 9: 0.000, + } + b = nx.betweenness_centrality(G, weight=None, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_florentine_families_graph(self): + """Betweenness centrality: Florentine families graph""" + G = nx.florentine_families_graph() + b_answer = { + "Acciaiuoli": 0.000, + "Albizzi": 0.212, + "Barbadori": 0.093, + "Bischeri": 0.104, + "Castellani": 0.055, + "Ginori": 0.000, + "Guadagni": 0.255, + "Lamberteschi": 0.000, + "Medici": 0.522, + "Pazzi": 0.000, + "Peruzzi": 0.022, + "Ridolfi": 0.114, + "Salviati": 0.143, + "Strozzi": 0.103, + "Tornabuoni": 0.092, + } + + b = nx.betweenness_centrality(G, weight=None, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_les_miserables_graph(self): + """Betweenness centrality: Les Miserables graph""" + G = nx.les_miserables_graph() + b_answer = { + "Napoleon": 0.000, + "Myriel": 0.177, + "MlleBaptistine": 0.000, + "MmeMagloire": 0.000, + "CountessDeLo": 0.000, + "Geborand": 0.000, + "Champtercier": 0.000, + "Cravatte": 0.000, + "Count": 0.000, + "OldMan": 0.000, + "Valjean": 0.570, + "Labarre": 0.000, + "Marguerite": 0.000, + "MmeDeR": 0.000, + "Isabeau": 0.000, + "Gervais": 0.000, + "Listolier": 0.000, + "Tholomyes": 0.041, + "Fameuil": 0.000, + "Blacheville": 0.000, + "Favourite": 0.000, + "Dahlia": 0.000, + "Zephine": 0.000, + "Fantine": 0.130, + "MmeThenardier": 0.029, + "Thenardier": 0.075, + "Cosette": 0.024, + "Javert": 0.054, + "Fauchelevent": 0.026, + "Bamatabois": 0.008, + "Perpetue": 0.000, + "Simplice": 0.009, + "Scaufflaire": 0.000, + "Woman1": 0.000, + "Judge": 0.000, + "Champmathieu": 0.000, + "Brevet": 0.000, + "Chenildieu": 0.000, + "Cochepaille": 0.000, + "Pontmercy": 0.007, + "Boulatruelle": 0.000, + "Eponine": 0.011, + "Anzelma": 0.000, + "Woman2": 0.000, + "MotherInnocent": 0.000, + "Gribier": 0.000, + "MmeBurgon": 0.026, + "Jondrette": 0.000, + "Gavroche": 0.165, + "Gillenormand": 0.020, + "Magnon": 0.000, + "MlleGillenormand": 0.048, + "MmePontmercy": 0.000, + "MlleVaubois": 0.000, + "LtGillenormand": 0.000, + "Marius": 0.132, + "BaronessT": 0.000, + "Mabeuf": 0.028, + "Enjolras": 0.043, + "Combeferre": 0.001, + "Prouvaire": 0.000, + "Feuilly": 0.001, + "Courfeyrac": 0.005, + "Bahorel": 0.002, + "Bossuet": 0.031, + "Joly": 0.002, + "Grantaire": 0.000, + "MotherPlutarch": 0.000, + "Gueulemer": 0.005, + "Babet": 0.005, + "Claquesous": 0.005, + "Montparnasse": 0.004, + "Toussaint": 0.000, + "Child1": 0.000, + "Child2": 0.000, + "Brujon": 0.000, + "MmeHucheloup": 0.000, + } + + b = nx.betweenness_centrality(G, weight=None, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_ladder_graph(self): + """Betweenness centrality: Ladder graph""" + G = nx.Graph() # ladder_graph(3) + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)]) + b_answer = {0: 1.667, 1: 1.667, 2: 6.667, 3: 6.667, 4: 1.667, 5: 1.667} + for b in b_answer: + b_answer[b] /= 2 + b = nx.betweenness_centrality(G, weight=None, normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_disconnected_path(self): + """Betweenness centrality: disconnected path""" + G = nx.Graph() + nx.add_path(G, [0, 1, 2]) + nx.add_path(G, [3, 4, 5, 6]) + b_answer = {0: 0, 1: 1, 2: 0, 3: 0, 4: 2, 5: 2, 6: 0} + b = nx.betweenness_centrality(G, weight=None, normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_disconnected_path_endpoints(self): + """Betweenness centrality: disconnected path endpoints""" + G = nx.Graph() + nx.add_path(G, [0, 1, 2]) + nx.add_path(G, [3, 4, 5, 6]) + b_answer = {0: 2, 1: 3, 2: 2, 3: 3, 4: 5, 5: 5, 6: 3} + b = nx.betweenness_centrality(G, weight=None, normalized=False, endpoints=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + # normalized = True case + b = nx.betweenness_centrality(G, weight=None, normalized=True, endpoints=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n] / 21, abs=1e-7) + + def test_directed_path(self): + """Betweenness centrality: directed path""" + G = nx.DiGraph() + nx.add_path(G, [0, 1, 2]) + b = nx.betweenness_centrality(G, weight=None, normalized=False) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_directed_path_normalized(self): + """Betweenness centrality: directed path normalized""" + G = nx.DiGraph() + nx.add_path(G, [0, 1, 2]) + b = nx.betweenness_centrality(G, weight=None, normalized=True) + b_answer = {0: 0.0, 1: 0.5, 2: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + @pytest.mark.parametrize( + ("normalized", "endpoints", "is_directed", "k", "expected"), + [ + (True, True, True, None, {0: 1.0, 1: 0.4, 2: 0.4, 3: 0.4, 4: 0.4}), + (True, True, True, 1, {0: 1.0, 1: 1.0, 2: 0.25, 3: 0.25, 4: 0.25}), + (True, True, False, None, {0: 1.0, 1: 0.4, 2: 0.4, 3: 0.4, 4: 0.4}), + (True, True, False, 1, {0: 1.0, 1: 1.0, 2: 0.25, 3: 0.25, 4: 0.25}), + (True, False, True, None, {0: 1.0, 1: 0, 2: 0.0, 3: 0.0, 4: 0.0}), + (True, False, True, 1, {0: 1.0, 1: math.nan, 2: 0.0, 3: 0.0, 4: 0.0}), + (True, False, False, None, {0: 1.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), + (True, False, False, 1, {0: 1.0, 1: math.nan, 2: 0.0, 3: 0.0, 4: 0.0}), + (False, True, True, None, {0: 20.0, 1: 8.0, 2: 8.0, 3: 8.0, 4: 8.0}), + (False, True, True, 1, {0: 20.0, 1: 20.0, 2: 5.0, 3: 5.0, 4: 5.0}), + (False, True, False, None, {0: 10.0, 1: 4.0, 2: 4.0, 3: 4.0, 4: 4.0}), + (False, True, False, 1, {0: 10.0, 1: 10.0, 2: 2.5, 3: 2.5, 4: 2.5}), + (False, False, True, None, {0: 12.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), + (False, False, True, 1, {0: 12.0, 1: math.nan, 2: 0.0, 3: 0.0, 4: 0.0}), + (False, False, False, None, {0: 6.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0}), + (False, False, False, 1, {0: 6.0, 1: math.nan, 2: 0.0, 3: 0.0, 4: 0.0}), + ], + ) + def test_scale_with_k_on_star_graph( + self, normalized, endpoints, is_directed, k, expected + ): + # seed=1 selects node 1 as the initial node when using k=1. + # Recall node 0 is the center of the star graph. + G = nx.star_graph(4) + if is_directed: + G = G.to_directed() + b = nx.betweenness_centrality( + G, k=k, seed=1, endpoints=endpoints, normalized=normalized + ) + assert b == pytest.approx(expected, nan_ok=True) + + @pytest.mark.parametrize( + ("normalized", "endpoints", "is_directed", "k", "expected"), + [ + ( + *(True, True, True, None), # Use *() splatting for better autoformat + {0: 14 / 20, 1: 14 / 20, 2: 14 / 20, 3: 14 / 20, 4: 14 / 20}, + ), + ( + *(True, True, True, 3), + {0: 9 / 12, 1: 11 / 12, 2: 9 / 12, 3: 6 / 12, 4: 7 / 12}, + ), + ( + *(True, True, False, None), + {0: 10 / 20, 1: 10 / 20, 2: 10 / 20, 3: 10 / 20, 4: 10 / 20}, + ), + ( + *(True, True, False, 3), + {0: 8 / 12, 1: 7 / 12, 2: 4 / 12, 3: 4 / 12, 4: 7 / 12}, + ), + ( + *(True, False, True, None), + {0: 6 / 12, 1: 6 / 12, 2: 6 / 12, 3: 6 / 12, 4: 6 / 12}, + ), + ( + *(True, False, True, 3), + # Use 6 instead of 9 for denominator for source nodes 0, 1, and 4 + {0: 3 / 6, 1: 5 / 6, 2: 6 / 9, 3: 3 / 9, 4: 1 / 6}, + ), + ( + *(True, False, False, None), + {0: 2 / 12, 1: 2 / 12, 2: 2 / 12, 3: 2 / 12, 4: 2 / 12}, + ), + ( + *(True, False, False, 3), + # Use 6 instead of 9 for denominator for source nodes 0, 1, and 4 + {0: 2 / 6, 1: 1 / 6, 2: 1 / 9, 3: 1 / 9, 4: 1 / 6}, + ), + (False, True, True, None, {0: 14, 1: 14, 2: 14, 3: 14, 4: 14}), + ( + *(False, True, True, 3), + {0: 9 * 5 / 3, 1: 11 * 5 / 3, 2: 9 * 5 / 3, 3: 6 * 5 / 3, 4: 7 * 5 / 3}, + ), + (False, True, False, None, {0: 5, 1: 5, 2: 5, 3: 5, 4: 5}), + ( + *(False, True, False, 3), + {0: 8 * 5 / 6, 1: 7 * 5 / 6, 2: 4 * 5 / 6, 3: 4 * 5 / 6, 4: 7 * 5 / 6}, + ), + (False, False, True, None, {0: 6, 1: 6, 2: 6, 3: 6, 4: 6}), + ( + *(False, False, True, 3), + # Use 2 instead of 3 for denominator for source nodes 0, 1, and 4 + {0: 3 * 4 / 2, 1: 5 * 4 / 2, 2: 6 * 4 / 3, 3: 3 * 4 / 3, 4: 1 * 4 / 2}, + ), + (False, False, False, None, {0: 1, 1: 1, 2: 1, 3: 1, 4: 1}), + ( + *(False, False, False, 3), + # Use 4 instead of 6 for denominator for source nodes 0, 1, and 4 + {0: 2 * 4 / 4, 1: 1 * 4 / 4, 2: 1 * 4 / 6, 3: 1 * 4 / 6, 4: 1 * 4 / 4}, + ), + ], + ) + def test_scale_with_k_on_cycle_graph( + self, normalized, endpoints, is_directed, k, expected + ): + # seed=1 selects nodes 0, 1, and 4 as the initial nodes when using k=3. + G = nx.cycle_graph(5, create_using=nx.DiGraph if is_directed else nx.Graph) + b = nx.betweenness_centrality( + G, k=k, seed=1, endpoints=endpoints, normalized=normalized + ) + assert b == pytest.approx(expected) + + def test_k_out_of_bounds_raises(self): + G = nx.cycle_graph(4) + with pytest.raises(ValueError, match="larger"): + nx.betweenness_centrality(G, k=5) + with pytest.raises(ValueError, match="negative"): + nx.betweenness_centrality(G, k=-1) + with pytest.raises(ZeroDivisionError): + nx.betweenness_centrality(G, k=0) + with pytest.raises(ZeroDivisionError): + nx.betweenness_centrality(G, k=0, normalized=False) + # Test edge case: use full population when k == len(G) + # Should we warn or raise instead? + b1 = nx.betweenness_centrality(G, k=4, endpoints=False) + b2 = nx.betweenness_centrality(G, endpoints=False) + assert b1 == b2 + + +class TestWeightedBetweennessCentrality: + def test_K5(self): + """Weighted betweenness centrality: K5""" + G = nx.complete_graph(5) + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P3_normalized(self): + """Weighted betweenness centrality: P3 normalized""" + G = nx.path_graph(3) + b = nx.betweenness_centrality(G, weight="weight", normalized=True) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P3(self): + """Weighted betweenness centrality: P3""" + G = nx.path_graph(3) + b_answer = {0: 0.0, 1: 1.0, 2: 0.0} + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_krackhardt_kite_graph(self): + """Weighted betweenness centrality: Krackhardt kite graph""" + G = nx.krackhardt_kite_graph() + b_answer = { + 0: 1.667, + 1: 1.667, + 2: 0.000, + 3: 7.333, + 4: 0.000, + 5: 16.667, + 6: 16.667, + 7: 28.000, + 8: 16.000, + 9: 0.000, + } + for b in b_answer: + b_answer[b] /= 2 + + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_krackhardt_kite_graph_normalized(self): + """Weighted betweenness centrality: + Krackhardt kite graph normalized + """ + G = nx.krackhardt_kite_graph() + b_answer = { + 0: 0.023, + 1: 0.023, + 2: 0.000, + 3: 0.102, + 4: 0.000, + 5: 0.231, + 6: 0.231, + 7: 0.389, + 8: 0.222, + 9: 0.000, + } + b = nx.betweenness_centrality(G, weight="weight", normalized=True) + + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_florentine_families_graph(self): + """Weighted betweenness centrality: + Florentine families graph""" + G = nx.florentine_families_graph() + b_answer = { + "Acciaiuoli": 0.000, + "Albizzi": 0.212, + "Barbadori": 0.093, + "Bischeri": 0.104, + "Castellani": 0.055, + "Ginori": 0.000, + "Guadagni": 0.255, + "Lamberteschi": 0.000, + "Medici": 0.522, + "Pazzi": 0.000, + "Peruzzi": 0.022, + "Ridolfi": 0.114, + "Salviati": 0.143, + "Strozzi": 0.103, + "Tornabuoni": 0.092, + } + + b = nx.betweenness_centrality(G, weight="weight", normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_les_miserables_graph(self): + """Weighted betweenness centrality: Les Miserables graph""" + G = nx.les_miserables_graph() + b_answer = { + "Napoleon": 0.000, + "Myriel": 0.177, + "MlleBaptistine": 0.000, + "MmeMagloire": 0.000, + "CountessDeLo": 0.000, + "Geborand": 0.000, + "Champtercier": 0.000, + "Cravatte": 0.000, + "Count": 0.000, + "OldMan": 0.000, + "Valjean": 0.454, + "Labarre": 0.000, + "Marguerite": 0.009, + "MmeDeR": 0.000, + "Isabeau": 0.000, + "Gervais": 0.000, + "Listolier": 0.000, + "Tholomyes": 0.066, + "Fameuil": 0.000, + "Blacheville": 0.000, + "Favourite": 0.000, + "Dahlia": 0.000, + "Zephine": 0.000, + "Fantine": 0.114, + "MmeThenardier": 0.046, + "Thenardier": 0.129, + "Cosette": 0.075, + "Javert": 0.193, + "Fauchelevent": 0.026, + "Bamatabois": 0.080, + "Perpetue": 0.000, + "Simplice": 0.001, + "Scaufflaire": 0.000, + "Woman1": 0.000, + "Judge": 0.000, + "Champmathieu": 0.000, + "Brevet": 0.000, + "Chenildieu": 0.000, + "Cochepaille": 0.000, + "Pontmercy": 0.023, + "Boulatruelle": 0.000, + "Eponine": 0.023, + "Anzelma": 0.000, + "Woman2": 0.000, + "MotherInnocent": 0.000, + "Gribier": 0.000, + "MmeBurgon": 0.026, + "Jondrette": 0.000, + "Gavroche": 0.285, + "Gillenormand": 0.024, + "Magnon": 0.005, + "MlleGillenormand": 0.036, + "MmePontmercy": 0.005, + "MlleVaubois": 0.000, + "LtGillenormand": 0.015, + "Marius": 0.072, + "BaronessT": 0.004, + "Mabeuf": 0.089, + "Enjolras": 0.003, + "Combeferre": 0.000, + "Prouvaire": 0.000, + "Feuilly": 0.004, + "Courfeyrac": 0.001, + "Bahorel": 0.007, + "Bossuet": 0.028, + "Joly": 0.000, + "Grantaire": 0.036, + "MotherPlutarch": 0.000, + "Gueulemer": 0.025, + "Babet": 0.015, + "Claquesous": 0.042, + "Montparnasse": 0.050, + "Toussaint": 0.011, + "Child1": 0.000, + "Child2": 0.000, + "Brujon": 0.002, + "MmeHucheloup": 0.034, + } + + b = nx.betweenness_centrality(G, weight="weight", normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_ladder_graph(self): + """Weighted betweenness centrality: Ladder graph""" + G = nx.Graph() # ladder_graph(3) + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)]) + b_answer = {0: 1.667, 1: 1.667, 2: 6.667, 3: 6.667, 4: 1.667, 5: 1.667} + for b in b_answer: + b_answer[b] /= 2 + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_G(self): + """Weighted betweenness centrality: G""" + G = weighted_G() + b_answer = {0: 2.0, 1: 0.0, 2: 4.0, 3: 3.0, 4: 4.0, 5: 0.0} + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_G2(self): + """Weighted betweenness centrality: G2""" + G = nx.DiGraph() + G.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + + b_answer = {"y": 5.0, "x": 5.0, "s": 4.0, "u": 2.0, "v": 2.0} + + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_G3(self): + """Weighted betweenness centrality: G3""" + G = nx.MultiGraph(weighted_G()) + es = list(G.edges(data=True))[::2] # duplicate every other edge + G.add_edges_from(es) + b_answer = {0: 2.0, 1: 0.0, 2: 4.0, 3: 3.0, 4: 4.0, 5: 0.0} + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_G4(self): + """Weighted betweenness centrality: G4""" + G = nx.MultiDiGraph() + G.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("s", "x", 6), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("x", "y", 3), + ("y", "s", 7), + ("y", "v", 6), + ("y", "v", 6), + ] + ) + + b_answer = {"y": 5.0, "x": 5.0, "s": 4.0, "u": 2.0, "v": 2.0} + + b = nx.betweenness_centrality(G, weight="weight", normalized=False) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + +class TestEdgeBetweennessCentrality: + def test_K5(self): + """Edge betweenness centrality: K5""" + G = nx.complete_graph(5) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer = dict.fromkeys(G.edges(), 1) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_K5(self): + """Edge betweenness centrality: K5""" + G = nx.complete_graph(5) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer = dict.fromkeys(G.edges(), 1 / 10) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_C4(self): + """Edge betweenness centrality: C4""" + G = nx.cycle_graph(4) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer = {(0, 1): 2, (0, 3): 2, (1, 2): 2, (2, 3): 2} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n] / 6, abs=1e-7) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G = nx.path_graph(4) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_P4(self): + """Edge betweenness centrality: P4""" + G = nx.path_graph(4) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=True) + b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n] / 6, abs=1e-7) + + def test_balanced_tree(self): + """Edge betweenness centrality: balanced tree""" + G = nx.balanced_tree(r=2, h=2) + b = nx.edge_betweenness_centrality(G, weight=None, normalized=False) + b_answer = {(0, 1): 12, (0, 2): 12, (1, 3): 6, (1, 4): 6, (2, 5): 6, (2, 6): 6} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_edge_betweenness_k(self): + """Ensure setting `k` properly limits the number of source nodes.""" + G = nx.path_graph(3) + # This choice of `k` and `seed` selects nodes 0 and 2. + # There is only one shortest path between any two pairs of nodes. + # With source nodes 0 and 2, this means that both edges are part of + # three shortest paths: + # For (0, 1): sp(0, 1), sp(0, 2), sp(2, 0). + # For (1, 2): sp(0, 2), sp(2, 0), sp(2, 1). + # We normalize by 2 because the graph is undirected, and by + # `k / n = 2 / 3` because we are only considering a subset of source + # nodes. + # This means the final eb centralities should be 3 / 2 / (2 / 3) = 9 / 4. + eb = nx.edge_betweenness_centrality(G, k=2, seed=42, normalized=False) + assert eb == {(0, 1): 9 / 4, (1, 2): 9 / 4} + # When normalization is `True`, we instead divide by the number of total + # `(s, t)` pairs, i.e. `k * (n - 1) = 4`, meaning we get an eb of `3 / 4`. + eb = nx.edge_betweenness_centrality(G, k=2, seed=42, normalized=True) + assert eb == {(0, 1): 3 / 4, (1, 2): 3 / 4} + + +class TestWeightedEdgeBetweennessCentrality: + def test_K5(self): + """Edge betweenness centrality: K5""" + G = nx.complete_graph(5) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = dict.fromkeys(G.edges(), 1) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_C4(self): + """Edge betweenness centrality: C4""" + G = nx.cycle_graph(4) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = {(0, 1): 2, (0, 3): 2, (1, 2): 2, (2, 3): 2} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G = nx.path_graph(4) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = {(0, 1): 3, (1, 2): 4, (2, 3): 3} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_balanced_tree(self): + """Edge betweenness centrality: balanced tree""" + G = nx.balanced_tree(r=2, h=2) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = {(0, 1): 12, (0, 2): 12, (1, 3): 6, (1, 4): 6, (2, 5): 6, (2, 6): 6} + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_weighted_graph(self): + """Edge betweenness centrality: weighted""" + eList = [ + (0, 1, 5), + (0, 2, 4), + (0, 3, 3), + (0, 4, 2), + (1, 2, 4), + (1, 3, 1), + (1, 4, 3), + (2, 4, 5), + (3, 4, 4), + ] + G = nx.Graph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = { + (0, 1): 0.0, + (0, 2): 1.0, + (0, 3): 2.0, + (0, 4): 1.0, + (1, 2): 2.0, + (1, 3): 3.5, + (1, 4): 1.5, + (2, 4): 1.0, + (3, 4): 0.5, + } + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_weighted_graph(self): + """Edge betweenness centrality: normalized weighted""" + eList = [ + (0, 1, 5), + (0, 2, 4), + (0, 3, 3), + (0, 4, 2), + (1, 2, 4), + (1, 3, 1), + (1, 4, 3), + (2, 4, 5), + (3, 4, 4), + ] + G = nx.Graph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=True) + b_answer = { + (0, 1): 0.0, + (0, 2): 1.0, + (0, 3): 2.0, + (0, 4): 1.0, + (1, 2): 2.0, + (1, 3): 3.5, + (1, 4): 1.5, + (2, 4): 1.0, + (3, 4): 0.5, + } + norm = len(G) * (len(G) - 1) / 2 + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n] / norm, abs=1e-7) + + def test_weighted_multigraph(self): + """Edge betweenness centrality: weighted multigraph""" + eList = [ + (0, 1, 5), + (0, 1, 4), + (0, 2, 4), + (0, 3, 3), + (0, 3, 3), + (0, 4, 2), + (1, 2, 4), + (1, 3, 1), + (1, 3, 2), + (1, 4, 3), + (1, 4, 4), + (2, 4, 5), + (3, 4, 4), + (3, 4, 4), + ] + G = nx.MultiGraph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=False) + b_answer = { + (0, 1, 0): 0.0, + (0, 1, 1): 0.5, + (0, 2, 0): 1.0, + (0, 3, 0): 0.75, + (0, 3, 1): 0.75, + (0, 4, 0): 1.0, + (1, 2, 0): 2.0, + (1, 3, 0): 3.0, + (1, 3, 1): 0.0, + (1, 4, 0): 1.5, + (1, 4, 1): 0.0, + (2, 4, 0): 1.0, + (3, 4, 0): 0.25, + (3, 4, 1): 0.25, + } + for n in sorted(G.edges(keys=True)): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_weighted_multigraph(self): + """Edge betweenness centrality: normalized weighted multigraph""" + eList = [ + (0, 1, 5), + (0, 1, 4), + (0, 2, 4), + (0, 3, 3), + (0, 3, 3), + (0, 4, 2), + (1, 2, 4), + (1, 3, 1), + (1, 3, 2), + (1, 4, 3), + (1, 4, 4), + (2, 4, 5), + (3, 4, 4), + (3, 4, 4), + ] + G = nx.MultiGraph() + G.add_weighted_edges_from(eList) + b = nx.edge_betweenness_centrality(G, weight="weight", normalized=True) + b_answer = { + (0, 1, 0): 0.0, + (0, 1, 1): 0.5, + (0, 2, 0): 1.0, + (0, 3, 0): 0.75, + (0, 3, 1): 0.75, + (0, 4, 0): 1.0, + (1, 2, 0): 2.0, + (1, 3, 0): 3.0, + (1, 3, 1): 0.0, + (1, 4, 0): 1.5, + (1, 4, 1): 0.0, + (2, 4, 0): 1.0, + (3, 4, 0): 0.25, + (3, 4, 1): 0.25, + } + norm = len(G) * (len(G) - 1) / 2 + for n in sorted(G.edges(keys=True)): + assert b[n] == pytest.approx(b_answer[n] / norm, abs=1e-7) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py new file mode 100644 index 0000000000000000000000000000000000000000..33bb6f7e8dbb17ab83efc387249046696a5cac32 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py @@ -0,0 +1,354 @@ +import pytest + +import networkx as nx + + +class TestSubsetBetweennessCentrality: + def test_K5(self): + """Betweenness Centrality Subset: K5""" + G = nx.complete_graph(5) + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[1, 3], weight=None + ) + b_answer = {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.0, 4: 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5_directed(self): + """Betweenness Centrality Subset: P5 directed""" + G = nx.DiGraph() + nx.add_path(G, range(5)) + b_answer = {0: 0, 1: 1, 2: 1, 3: 0, 4: 0, 5: 0} + b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5(self): + """Betweenness Centrality Subset: P5""" + G = nx.Graph() + nx.add_path(G, range(5)) + b_answer = {0: 0, 1: 0.5, 2: 0.5, 3: 0, 4: 0, 5: 0} + b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5_multiple_target(self): + """Betweenness Centrality Subset: P5 multiple target""" + G = nx.Graph() + nx.add_path(G, range(5)) + b_answer = {0: 0, 1: 1, 2: 1, 3: 0.5, 4: 0, 5: 0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box(self): + """Betweenness Centrality Subset: box""" + G = nx.Graph() + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)]) + b_answer = {0: 0, 1: 0.25, 2: 0.25, 3: 0} + b = nx.betweenness_centrality_subset(G, sources=[0], targets=[3], weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box_and_path(self): + """Betweenness Centrality Subset: box and path""" + G = nx.Graph() + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)]) + b_answer = {0: 0, 1: 0.5, 2: 0.5, 3: 0.5, 4: 0, 5: 0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box_and_path2(self): + """Betweenness Centrality Subset: box and path multiple target""" + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (2, 3), (1, 20), (20, 3), (3, 4)]) + b_answer = {0: 0, 1: 1.0, 2: 0.5, 20: 0.5, 3: 0.5, 4: 0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_diamond_multi_path(self): + """Betweenness Centrality Subset: Diamond Multi Path""" + G = nx.Graph() + G.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 10), + (10, 11), + (11, 12), + (12, 9), + (2, 6), + (3, 6), + (4, 6), + (5, 7), + (7, 8), + (6, 8), + (8, 9), + ] + ) + b = nx.betweenness_centrality_subset(G, sources=[1], targets=[9], weight=None) + + expected_b = { + 1: 0, + 2: 1.0 / 10, + 3: 1.0 / 10, + 4: 1.0 / 10, + 5: 1.0 / 10, + 6: 3.0 / 10, + 7: 1.0 / 10, + 8: 4.0 / 10, + 9: 0, + 10: 1.0 / 10, + 11: 1.0 / 10, + 12: 1.0 / 10, + } + + for n in sorted(G): + assert b[n] == pytest.approx(expected_b[n], abs=1e-7) + + def test_normalized_p2(self): + """ + Betweenness Centrality Subset: Normalized P2 + if n <= 2: no normalization, betweenness centrality should be 0 for all nodes. + """ + G = nx.Graph() + nx.add_path(G, range(2)) + b_answer = {0: 0, 1: 0.0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[1], normalized=True, weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_P5_directed(self): + """Betweenness Centrality Subset: Normalized Directed P5""" + G = nx.DiGraph() + nx.add_path(G, range(5)) + b_answer = {0: 0, 1: 1.0 / 12.0, 2: 1.0 / 12.0, 3: 0, 4: 0, 5: 0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[3], normalized=True, weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_weighted_graph(self): + """Betweenness Centrality Subset: Weighted Graph""" + G = nx.DiGraph() + G.add_edge(0, 1, weight=3) + G.add_edge(0, 2, weight=2) + G.add_edge(0, 3, weight=6) + G.add_edge(0, 4, weight=4) + G.add_edge(1, 3, weight=5) + G.add_edge(1, 5, weight=5) + G.add_edge(2, 4, weight=1) + G.add_edge(3, 4, weight=2) + G.add_edge(3, 5, weight=1) + G.add_edge(4, 5, weight=4) + b_answer = {0: 0.0, 1: 0.0, 2: 0.5, 3: 0.5, 4: 0.5, 5: 0.0} + b = nx.betweenness_centrality_subset( + G, sources=[0], targets=[5], normalized=False, weight="weight" + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + +class TestEdgeSubsetBetweennessCentrality: + def test_K5(self): + """Edge betweenness subset centrality: K5""" + G = nx.complete_graph(5) + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[1, 3], weight=None + ) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 3)] = b_answer[(0, 1)] = 0.5 + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5_directed(self): + """Edge betweenness subset centrality: P5 directed""" + G = nx.DiGraph() + nx.add_path(G, range(5)) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 1 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5(self): + """Edge betweenness subset centrality: P5""" + G = nx.Graph() + nx.add_path(G, range(5)) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 0.5 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P5_multiple_target(self): + """Edge betweenness subset centrality: P5 multiple target""" + G = nx.Graph() + nx.add_path(G, range(5)) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 1 + b_answer[(3, 4)] = 0.5 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box(self): + """Edge betweenness subset centrality: box""" + G = nx.Graph() + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3)]) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(0, 2)] = 0.25 + b_answer[(1, 3)] = b_answer[(2, 3)] = 0.25 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box_and_path(self): + """Edge betweenness subset centrality: box and path""" + G = nx.Graph() + G.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)]) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(0, 2)] = 0.5 + b_answer[(1, 3)] = b_answer[(2, 3)] = 0.5 + b_answer[(3, 4)] = 0.5 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_box_and_path2(self): + """Edge betweenness subset centrality: box and path multiple target""" + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (2, 3), (1, 20), (20, 3), (3, 4)]) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = 1.0 + b_answer[(1, 20)] = b_answer[(3, 20)] = 0.5 + b_answer[(1, 2)] = b_answer[(2, 3)] = 0.5 + b_answer[(3, 4)] = 0.5 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3, 4], weight=None + ) + for n in sorted(G.edges()): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_diamond_multi_path(self): + """Edge betweenness subset centrality: Diamond Multi Path""" + G = nx.Graph() + G.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (1, 10), + (10, 11), + (11, 12), + (12, 9), + (2, 6), + (3, 6), + (4, 6), + (5, 7), + (7, 8), + (6, 8), + (8, 9), + ] + ) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(8, 9)] = 0.4 + b_answer[(6, 8)] = b_answer[(7, 8)] = 0.2 + b_answer[(2, 6)] = b_answer[(3, 6)] = b_answer[(4, 6)] = 0.2 / 3.0 + b_answer[(1, 2)] = b_answer[(1, 3)] = b_answer[(1, 4)] = 0.2 / 3.0 + b_answer[(5, 7)] = 0.2 + b_answer[(1, 5)] = 0.2 + b_answer[(9, 12)] = 0.1 + b_answer[(11, 12)] = b_answer[(10, 11)] = b_answer[(1, 10)] = 0.1 + b = nx.edge_betweenness_centrality_subset( + G, sources=[1], targets=[9], weight=None + ) + for n in G.edges(): + sort_n = tuple(sorted(n)) + assert b[n] == pytest.approx(b_answer[sort_n], abs=1e-7) + + def test_normalized_p1(self): + """ + Edge betweenness subset centrality: P1 + if n <= 1: no normalization b=0 for all nodes + """ + G = nx.Graph() + nx.add_path(G, range(1)) + b_answer = dict.fromkeys(G.edges(), 0) + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[0], normalized=True, weight=None + ) + for n in G.edges(): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_normalized_P5_directed(self): + """Edge betweenness subset centrality: Normalized Directed P5""" + G = nx.DiGraph() + nx.add_path(G, range(5)) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 1)] = b_answer[(1, 2)] = b_answer[(2, 3)] = 0.05 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[3], normalized=True, weight=None + ) + for n in G.edges(): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_weighted_graph(self): + """Edge betweenness subset centrality: Weighted Graph""" + G = nx.DiGraph() + G.add_edge(0, 1, weight=3) + G.add_edge(0, 2, weight=2) + G.add_edge(0, 3, weight=6) + G.add_edge(0, 4, weight=4) + G.add_edge(1, 3, weight=5) + G.add_edge(1, 5, weight=5) + G.add_edge(2, 4, weight=1) + G.add_edge(3, 4, weight=2) + G.add_edge(3, 5, weight=1) + G.add_edge(4, 5, weight=4) + b_answer = dict.fromkeys(G.edges(), 0) + b_answer[(0, 2)] = b_answer[(2, 4)] = b_answer[(4, 5)] = 0.5 + b_answer[(0, 3)] = b_answer[(3, 5)] = 0.5 + b = nx.edge_betweenness_centrality_subset( + G, sources=[0], targets=[5], normalized=False, weight="weight" + ) + for n in G.edges(): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + +def test_equivalence_non_subset(): + """Assert that subset betweenness centrality with all nodes in the + subset is equivalent to full betweenness centrality. + """ + G = nx.path_graph(10, create_using=nx.DiGraph) + + assert nx.betweenness_centrality(G) == nx.betweenness_centrality_subset( + G, sources=G.nodes(), targets=G.nodes(), normalized=True + ) + assert nx.edge_betweenness_centrality(G) == nx.edge_betweenness_centrality_subset( + G, sources=G.nodes(), targets=G.nodes(), normalized=True + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_closeness_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_closeness_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..5419560fe7ea3cd974f796ba373af7c514d2f9dc --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_closeness_centrality.py @@ -0,0 +1,274 @@ +""" +Tests for closeness centrality. +""" + +import pytest + +import networkx as nx + + +@pytest.fixture() +def undirected_G(): + G = nx.fast_gnp_random_graph(n=100, p=0.6, seed=123) + cc = nx.closeness_centrality(G) + return G, cc + + +class TestClosenessCentrality: + def test_wf_improved(self): + G = nx.union(nx.path_graph(4), nx.path_graph([4, 5, 6])) + c = nx.closeness_centrality(G) + cwf = nx.closeness_centrality(G, wf_improved=False) + res = {0: 0.25, 1: 0.375, 2: 0.375, 3: 0.25, 4: 0.222, 5: 0.333, 6: 0.222} + wf_res = {0: 0.5, 1: 0.75, 2: 0.75, 3: 0.5, 4: 0.667, 5: 1.0, 6: 0.667} + for n in G: + assert c[n] == pytest.approx(res[n], abs=1e-3) + assert cwf[n] == pytest.approx(wf_res[n], abs=1e-3) + + def test_digraph(self): + G = nx.path_graph(3, create_using=nx.DiGraph) + c = nx.closeness_centrality(G) + cr = nx.closeness_centrality(G.reverse()) + d = {0: 0.0, 1: 0.500, 2: 0.667} + dr = {0: 0.667, 1: 0.500, 2: 0.0} + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + assert cr[n] == pytest.approx(dr[n], abs=1e-3) + + def test_k5_closeness(self): + G = nx.complete_graph(5) + c = nx.closeness_centrality(G) + d = {0: 1.000, 1: 1.000, 2: 1.000, 3: 1.000, 4: 1.000} + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_p3_closeness(self): + G = nx.path_graph(3) + c = nx.closeness_centrality(G) + d = {0: 0.667, 1: 1.000, 2: 0.667} + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_krackhardt_closeness(self): + G = nx.krackhardt_kite_graph() + c = nx.closeness_centrality(G) + d = { + 0: 0.529, + 1: 0.529, + 2: 0.500, + 3: 0.600, + 4: 0.500, + 5: 0.643, + 6: 0.643, + 7: 0.600, + 8: 0.429, + 9: 0.310, + } + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_florentine_families_closeness(self): + G = nx.florentine_families_graph() + c = nx.closeness_centrality(G) + d = { + "Acciaiuoli": 0.368, + "Albizzi": 0.483, + "Barbadori": 0.4375, + "Bischeri": 0.400, + "Castellani": 0.389, + "Ginori": 0.333, + "Guadagni": 0.467, + "Lamberteschi": 0.326, + "Medici": 0.560, + "Pazzi": 0.286, + "Peruzzi": 0.368, + "Ridolfi": 0.500, + "Salviati": 0.389, + "Strozzi": 0.4375, + "Tornabuoni": 0.483, + } + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_les_miserables_closeness(self): + G = nx.les_miserables_graph() + c = nx.closeness_centrality(G) + d = { + "Napoleon": 0.302, + "Myriel": 0.429, + "MlleBaptistine": 0.413, + "MmeMagloire": 0.413, + "CountessDeLo": 0.302, + "Geborand": 0.302, + "Champtercier": 0.302, + "Cravatte": 0.302, + "Count": 0.302, + "OldMan": 0.302, + "Valjean": 0.644, + "Labarre": 0.394, + "Marguerite": 0.413, + "MmeDeR": 0.394, + "Isabeau": 0.394, + "Gervais": 0.394, + "Listolier": 0.341, + "Tholomyes": 0.392, + "Fameuil": 0.341, + "Blacheville": 0.341, + "Favourite": 0.341, + "Dahlia": 0.341, + "Zephine": 0.341, + "Fantine": 0.461, + "MmeThenardier": 0.461, + "Thenardier": 0.517, + "Cosette": 0.478, + "Javert": 0.517, + "Fauchelevent": 0.402, + "Bamatabois": 0.427, + "Perpetue": 0.318, + "Simplice": 0.418, + "Scaufflaire": 0.394, + "Woman1": 0.396, + "Judge": 0.404, + "Champmathieu": 0.404, + "Brevet": 0.404, + "Chenildieu": 0.404, + "Cochepaille": 0.404, + "Pontmercy": 0.373, + "Boulatruelle": 0.342, + "Eponine": 0.396, + "Anzelma": 0.352, + "Woman2": 0.402, + "MotherInnocent": 0.398, + "Gribier": 0.288, + "MmeBurgon": 0.344, + "Jondrette": 0.257, + "Gavroche": 0.514, + "Gillenormand": 0.442, + "Magnon": 0.335, + "MlleGillenormand": 0.442, + "MmePontmercy": 0.315, + "MlleVaubois": 0.308, + "LtGillenormand": 0.365, + "Marius": 0.531, + "BaronessT": 0.352, + "Mabeuf": 0.396, + "Enjolras": 0.481, + "Combeferre": 0.392, + "Prouvaire": 0.357, + "Feuilly": 0.392, + "Courfeyrac": 0.400, + "Bahorel": 0.394, + "Bossuet": 0.475, + "Joly": 0.394, + "Grantaire": 0.358, + "MotherPlutarch": 0.285, + "Gueulemer": 0.463, + "Babet": 0.463, + "Claquesous": 0.452, + "Montparnasse": 0.458, + "Toussaint": 0.402, + "Child1": 0.342, + "Child2": 0.342, + "Brujon": 0.380, + "MmeHucheloup": 0.353, + } + for n in G: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_weighted_closeness(self): + edges = [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + XG = nx.Graph() + XG.add_weighted_edges_from(edges) + c = nx.closeness_centrality(XG, distance="weight") + d = {"y": 0.200, "x": 0.286, "s": 0.138, "u": 0.235, "v": 0.200} + for n in sorted(XG): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + +class TestIncrementalClosenessCentrality: + @staticmethod + def pick_add_edge(G): + u = nx.utils.arbitrary_element(G) + possible_nodes = set(G) - (set(G.neighbors(u)) | {u}) + v = nx.utils.arbitrary_element(possible_nodes) + return (u, v) + + @staticmethod + def pick_remove_edge(G): + u = nx.utils.arbitrary_element(G) + possible_nodes = list(G.neighbors(u)) + v = nx.utils.arbitrary_element(possible_nodes) + return (u, v) + + def test_directed_raises(self): + dir_G = nx.gn_graph(n=5) + prev_cc = None + edge = self.pick_add_edge(dir_G) + with pytest.raises(nx.NetworkXNotImplemented): + nx.incremental_closeness_centrality(dir_G, edge, prev_cc, insertion=True) + + def test_wrong_size_prev_cc_raises(self, undirected_G): + G, prev_cc = undirected_G + edge = self.pick_add_edge(G) + prev_cc.pop(0) + with pytest.raises(nx.NetworkXError): + nx.incremental_closeness_centrality(G, edge, prev_cc, insertion=True) + + def test_wrong_nodes_prev_cc_raises(self, undirected_G): + G, prev_cc = undirected_G + + edge = self.pick_add_edge(G) + num_nodes = len(prev_cc) + prev_cc.pop(0) + prev_cc[num_nodes] = 0.5 + with pytest.raises(nx.NetworkXError): + nx.incremental_closeness_centrality(G, edge, prev_cc, insertion=True) + + def test_zero_centrality(self): + G = nx.path_graph(3) + prev_cc = nx.closeness_centrality(G) + edge = self.pick_remove_edge(G) + test_cc = nx.incremental_closeness_centrality(G, edge, prev_cc, insertion=False) + G.remove_edges_from([edge]) + real_cc = nx.closeness_centrality(G) + shared_items = set(test_cc.items()) & set(real_cc.items()) + assert len(shared_items) == len(real_cc) + assert 0 in test_cc.values() + + def test_incremental(self, undirected_G): + # Check that incremental and regular give same output + G, _ = undirected_G + prev_cc = None + for i in range(5): + if i % 2 == 0: + # Remove an edge + insert = False + edge = self.pick_remove_edge(G) + else: + # Add an edge + insert = True + edge = self.pick_add_edge(G) + + test_cc = nx.incremental_closeness_centrality(G, edge, prev_cc, insert) + + if insert: + G.add_edges_from([edge]) + else: + G.remove_edges_from([edge]) + + real_cc = nx.closeness_centrality(G) + + assert set(test_cc.items()) == set(real_cc.items()) + + prev_cc = test_cc diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..e79cd5c8d886c6664ae963d64ad12990d978dd52 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py @@ -0,0 +1,259 @@ +import pytest + +import networkx as nx +from networkx import approximate_current_flow_betweenness_centrality as approximate_cfbc +from networkx import edge_current_flow_betweenness_centrality as edge_current_flow + +np = pytest.importorskip("numpy") +pytest.importorskip("scipy") + + +class TestFlowBetweennessCentrality: + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + b_answer = {0: 0.25, 1: 0.25, 2: 0.25, 3: 0.25} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + G.add_edge(0, 1, weight=0.5, other=0.3) + b = nx.current_flow_betweenness_centrality(G, normalized=True, weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + wb_answer = {0: 0.2222222, 1: 0.2222222, 2: 0.30555555, 3: 0.30555555} + b = nx.current_flow_betweenness_centrality(G, normalized=True, weight="weight") + for n in sorted(G): + assert b[n] == pytest.approx(wb_answer[n], abs=1e-7) + wb_answer = {0: 0.2051282, 1: 0.2051282, 2: 0.33974358, 3: 0.33974358} + b = nx.current_flow_betweenness_centrality(G, normalized=True, weight="other") + for n in sorted(G): + assert b[n] == pytest.approx(wb_answer[n], abs=1e-7) + + def test_K4(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + for solver in ["full", "lu", "cg"]: + b = nx.current_flow_betweenness_centrality( + G, normalized=False, solver=solver + ) + b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4_normalized(self): + """Betweenness centrality: P4 normalized""" + G = nx.path_graph(4) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + b_answer = {0: 0, 1: 2.0 / 3, 2: 2.0 / 3, 3: 0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4(self): + """Betweenness centrality: P4""" + G = nx.path_graph(4) + b = nx.current_flow_betweenness_centrality(G, normalized=False) + b_answer = {0: 0, 1: 2, 2: 2, 3: 0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_star(self): + """Betweenness centrality: star""" + G = nx.Graph() + nx.add_star(G, ["a", "b", "c", "d"]) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + b_answer = {"a": 1.0, "b": 0.0, "c": 0.0, "d": 0.0} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_solvers2(self): + """Betweenness centrality: alternate solvers""" + G = nx.complete_graph(4) + for solver in ["full", "lu", "cg"]: + b = nx.current_flow_betweenness_centrality( + G, normalized=False, solver=solver + ) + b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + +class TestApproximateFlowBetweennessCentrality: + def test_K4_normalized(self): + "Approximate current-flow betweenness centrality: K4 normalized" + G = nx.complete_graph(4) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + epsilon = 0.1 + ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon) + for n in sorted(G): + np.testing.assert_allclose(b[n], ba[n], atol=epsilon) + + def test_K4(self): + "Approximate current-flow betweenness centrality: K4" + G = nx.complete_graph(4) + b = nx.current_flow_betweenness_centrality(G, normalized=False) + epsilon = 0.1 + ba = approximate_cfbc(G, normalized=False, epsilon=0.5 * epsilon) + for n in sorted(G): + np.testing.assert_allclose(b[n], ba[n], atol=epsilon * len(G) ** 2) + + def test_star(self): + "Approximate current-flow betweenness centrality: star" + G = nx.Graph() + nx.add_star(G, ["a", "b", "c", "d"]) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + epsilon = 0.1 + ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon) + for n in sorted(G): + np.testing.assert_allclose(b[n], ba[n], atol=epsilon) + + def test_grid(self): + "Approximate current-flow betweenness centrality: 2d grid" + G = nx.grid_2d_graph(4, 4) + b = nx.current_flow_betweenness_centrality(G, normalized=True) + epsilon = 0.1 + ba = approximate_cfbc(G, normalized=True, epsilon=0.5 * epsilon) + for n in sorted(G): + np.testing.assert_allclose(b[n], ba[n], atol=epsilon) + + def test_seed(self): + G = nx.complete_graph(4) + b = approximate_cfbc(G, normalized=False, epsilon=0.05, seed=1) + b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + np.testing.assert_allclose(b[n], b_answer[n], atol=0.1) + + def test_solvers(self): + "Approximate current-flow betweenness centrality: solvers" + G = nx.complete_graph(4) + epsilon = 0.1 + for solver in ["full", "lu", "cg"]: + b = approximate_cfbc( + G, normalized=False, solver=solver, epsilon=0.5 * epsilon + ) + b_answer = {0: 0.75, 1: 0.75, 2: 0.75, 3: 0.75} + for n in sorted(G): + np.testing.assert_allclose(b[n], b_answer[n], atol=epsilon) + + def test_lower_kmax(self): + G = nx.complete_graph(4) + with pytest.raises(nx.NetworkXError, match="Increase kmax or epsilon"): + nx.approximate_current_flow_betweenness_centrality(G, kmax=4) + + def test_sample_weight_positive_effect(self): + G = nx.complete_graph(4) + b1 = approximate_cfbc(G, epsilon=0.1, seed=42) + b2 = approximate_cfbc(G, epsilon=0.1, sample_weight=2.0, seed=42) + assert len(b1) == len(b2) == 4 + for node in G.nodes(): + assert node in b1 and node in b2 + assert isinstance(b1[node], float) and isinstance(b2[node], float) + + def test_sample_weight_validation(self): + G = nx.complete_graph(4) + + with pytest.raises( + nx.NetworkXError, + match="Sample weight must be positive. Got sample_weight=-1.0", + ): + approximate_cfbc(G, sample_weight=-1.0) + + with pytest.raises( + nx.NetworkXError, + match="Sample weight must be positive. Got sample_weight=0.0", + ): + approximate_cfbc(G, sample_weight=0.0) + + result = approximate_cfbc(G, sample_weight=0.1, seed=42) + assert len(result) == 4 + + def test_epsilon_validation(self): + G = nx.complete_graph(4) + + with pytest.raises( + nx.NetworkXError, match="Epsilon must be positive. Got epsilon=-0.1" + ): + approximate_cfbc(G, epsilon=-0.1) + + with pytest.raises( + nx.NetworkXError, match="Epsilon must be positive. Got epsilon=0.0" + ): + approximate_cfbc(G, epsilon=0.0) + + def test_normalization_edge_case_small_graph(self): + G = nx.path_graph(2) + + result_norm = approximate_cfbc(G, normalized=True, seed=42) + result_unnorm = approximate_cfbc(G, normalized=False, seed=42) + + assert len(result_norm) == 2 + assert len(result_unnorm) == 2 + assert all(v == 0.0 for v in result_norm.values()) + assert all(v == 0.0 for v in result_unnorm.values()) + + G1 = nx.Graph() + G1.add_node(0) + result1 = approximate_cfbc(G1, normalized=True, seed=42) + assert result1 == {0: 0.0} + + def test_sample_weight_interaction_with_kmax(self): + G = nx.complete_graph(4) + + with pytest.raises(nx.NetworkXError, match="Number random pairs k>kmax"): + approximate_cfbc(G, sample_weight=10.0, epsilon=0.01, kmax=10) + + +class TestWeightedFlowBetweennessCentrality: + pass + + +class TestEdgeFlowBetweennessCentrality: + def test_K4(self): + """Edge flow betweenness centrality: K4""" + G = nx.complete_graph(4) + b = edge_current_flow(G, normalized=True) + b_answer = dict.fromkeys(G.edges(), 0.25) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_K4_normalized(self): + """Edge flow betweenness centrality: K4""" + G = nx.complete_graph(4) + b = edge_current_flow(G, normalized=False) + b_answer = dict.fromkeys(G.edges(), 0.75) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_C4(self): + """Edge flow betweenness centrality: C4""" + G = nx.cycle_graph(4) + b = edge_current_flow(G, normalized=False) + b_answer = {(0, 1): 1.25, (0, 3): 1.25, (1, 2): 1.25, (2, 3): 1.25} + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G = nx.path_graph(4) + b = edge_current_flow(G, normalized=False) + b_answer = {(0, 1): 1.5, (1, 2): 2.0, (2, 3): 1.5} + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + +@pytest.mark.parametrize( + "centrality_func", + ( + nx.current_flow_betweenness_centrality, + nx.edge_current_flow_betweenness_centrality, + nx.approximate_current_flow_betweenness_centrality, + ), +) +def test_unconnected_graphs_betweenness_centrality(centrality_func): + G = nx.Graph([(1, 2), (3, 4)]) + G.add_node(5) + with pytest.raises(nx.NetworkXError, match="Graph not connected"): + centrality_func(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py new file mode 100644 index 0000000000000000000000000000000000000000..7b1611b07bbf890f5e45bba7a42c298bd8f4e749 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py @@ -0,0 +1,147 @@ +import pytest + +pytest.importorskip("numpy") +pytest.importorskip("scipy") + +import networkx as nx +from networkx import edge_current_flow_betweenness_centrality as edge_current_flow +from networkx import ( + edge_current_flow_betweenness_centrality_subset as edge_current_flow_subset, +) + + +class TestFlowBetweennessCentrality: + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_K4(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + # test weighted network + G.add_edge(0, 1, weight=0.5, other=0.3) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True, weight=None + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True, weight="other" + ) + b_answer = nx.current_flow_betweenness_centrality( + G, normalized=True, weight="other" + ) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4_normalized(self): + """Betweenness centrality: P4 normalized""" + G = nx.path_graph(4) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4(self): + """Betweenness centrality: P4""" + G = nx.path_graph(4) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_star(self): + """Betweenness centrality: star""" + G = nx.Graph() + nx.add_star(G, ["a", "b", "c", "d"]) + b = nx.current_flow_betweenness_centrality_subset( + G, list(G), list(G), normalized=True + ) + b_answer = nx.current_flow_betweenness_centrality(G, normalized=True) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + +# class TestWeightedFlowBetweennessCentrality(): +# pass + + +class TestEdgeFlowBetweennessCentrality: + def test_K4_normalized(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + b = edge_current_flow_subset(G, list(G), list(G), normalized=True) + b_answer = edge_current_flow(G, normalized=True) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_K4(self): + """Betweenness centrality: K4""" + G = nx.complete_graph(4) + b = edge_current_flow_subset(G, list(G), list(G), normalized=False) + b_answer = edge_current_flow(G, normalized=False) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + # test weighted network + G.add_edge(0, 1, weight=0.5, other=0.3) + b = edge_current_flow_subset(G, list(G), list(G), normalized=False, weight=None) + # weight is None => same as unweighted network + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + b = edge_current_flow_subset(G, list(G), list(G), normalized=False) + b_answer = edge_current_flow(G, normalized=False) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + b = edge_current_flow_subset( + G, list(G), list(G), normalized=False, weight="other" + ) + b_answer = edge_current_flow(G, normalized=False, weight="other") + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_C4(self): + """Edge betweenness centrality: C4""" + G = nx.cycle_graph(4) + b = edge_current_flow_subset(G, list(G), list(G), normalized=True) + b_answer = edge_current_flow(G, normalized=True) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) + + def test_P4(self): + """Edge betweenness centrality: P4""" + G = nx.path_graph(4) + b = edge_current_flow_subset(G, list(G), list(G), normalized=True) + b_answer = edge_current_flow(G, normalized=True) + for (s, t), v1 in b_answer.items(): + v2 = b.get((s, t), b.get((t, s))) + assert v1 == pytest.approx(v2, abs=1e-7) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_closeness.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_closeness.py new file mode 100644 index 0000000000000000000000000000000000000000..2528d622855938b8f569d4fb33309ebed1dbd7c8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_current_flow_closeness.py @@ -0,0 +1,43 @@ +import pytest + +pytest.importorskip("numpy") +pytest.importorskip("scipy") + +import networkx as nx + + +class TestFlowClosenessCentrality: + def test_K4(self): + """Closeness centrality: K4""" + G = nx.complete_graph(4) + b = nx.current_flow_closeness_centrality(G) + b_answer = {0: 2.0 / 3, 1: 2.0 / 3, 2: 2.0 / 3, 3: 2.0 / 3} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P4(self): + """Closeness centrality: P4""" + G = nx.path_graph(4) + b = nx.current_flow_closeness_centrality(G) + b_answer = {0: 1.0 / 6, 1: 1.0 / 4, 2: 1.0 / 4, 3: 1.0 / 6} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_star(self): + """Closeness centrality: star""" + G = nx.Graph() + nx.add_star(G, ["a", "b", "c", "d"]) + b = nx.current_flow_closeness_centrality(G) + b_answer = {"a": 1.0 / 3, "b": 0.6 / 3, "c": 0.6 / 3, "d": 0.6 / 3} + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_current_flow_closeness_centrality_not_connected(self): + G = nx.Graph() + G.add_nodes_from([1, 2, 3]) + with pytest.raises(nx.NetworkXError): + nx.current_flow_closeness_centrality(G) + + +class TestWeightedFlowClosenessCentrality: + pass diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_degree_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_degree_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..e39aa3b19f248acdd3a23e126e426bfd1c45c4c7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_degree_centrality.py @@ -0,0 +1,144 @@ +""" +Unit tests for degree centrality. +""" + +import pytest + +import networkx as nx + + +class TestDegreeCentrality: + def setup_method(self): + self.K = nx.krackhardt_kite_graph() + self.P3 = nx.path_graph(3) + self.K5 = nx.complete_graph(5) + + F = nx.Graph() # Florentine families + F.add_edge("Acciaiuoli", "Medici") + F.add_edge("Castellani", "Peruzzi") + F.add_edge("Castellani", "Strozzi") + F.add_edge("Castellani", "Barbadori") + F.add_edge("Medici", "Barbadori") + F.add_edge("Medici", "Ridolfi") + F.add_edge("Medici", "Tornabuoni") + F.add_edge("Medici", "Albizzi") + F.add_edge("Medici", "Salviati") + F.add_edge("Salviati", "Pazzi") + F.add_edge("Peruzzi", "Strozzi") + F.add_edge("Peruzzi", "Bischeri") + F.add_edge("Strozzi", "Ridolfi") + F.add_edge("Strozzi", "Bischeri") + F.add_edge("Ridolfi", "Tornabuoni") + F.add_edge("Tornabuoni", "Guadagni") + F.add_edge("Albizzi", "Ginori") + F.add_edge("Albizzi", "Guadagni") + F.add_edge("Bischeri", "Guadagni") + F.add_edge("Guadagni", "Lamberteschi") + self.F = F + + G = nx.DiGraph() + G.add_edge(0, 5) + G.add_edge(1, 5) + G.add_edge(2, 5) + G.add_edge(3, 5) + G.add_edge(4, 5) + G.add_edge(5, 6) + G.add_edge(5, 7) + G.add_edge(5, 8) + self.G = G + + def test_degree_centrality_1(self): + d = nx.degree_centrality(self.K5) + exact = dict(zip(range(5), [1] * 5)) + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + def test_degree_centrality_2(self): + d = nx.degree_centrality(self.P3) + exact = {0: 0.5, 1: 1, 2: 0.5} + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + def test_degree_centrality_3(self): + d = nx.degree_centrality(self.K) + exact = { + 0: 0.444, + 1: 0.444, + 2: 0.333, + 3: 0.667, + 4: 0.333, + 5: 0.556, + 6: 0.556, + 7: 0.333, + 8: 0.222, + 9: 0.111, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(float(f"{dc:.3f}"), abs=1e-7) + + def test_degree_centrality_4(self): + d = nx.degree_centrality(self.F) + names = sorted(self.F.nodes()) + dcs = [ + 0.071, + 0.214, + 0.143, + 0.214, + 0.214, + 0.071, + 0.286, + 0.071, + 0.429, + 0.071, + 0.214, + 0.214, + 0.143, + 0.286, + 0.214, + ] + exact = dict(zip(names, dcs)) + for n, dc in d.items(): + assert exact[n] == pytest.approx(float(f"{dc:.3f}"), abs=1e-7) + + def test_indegree_centrality(self): + d = nx.in_degree_centrality(self.G) + exact = { + 0: 0.0, + 1: 0.0, + 2: 0.0, + 3: 0.0, + 4: 0.0, + 5: 0.625, + 6: 0.125, + 7: 0.125, + 8: 0.125, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + def test_outdegree_centrality(self): + d = nx.out_degree_centrality(self.G) + exact = { + 0: 0.125, + 1: 0.125, + 2: 0.125, + 3: 0.125, + 4: 0.125, + 5: 0.375, + 6: 0.0, + 7: 0.0, + 8: 0.0, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + def test_small_graph_centrality(self): + G = nx.empty_graph(create_using=nx.DiGraph) + assert {} == nx.degree_centrality(G) + assert {} == nx.out_degree_centrality(G) + assert {} == nx.in_degree_centrality(G) + + G = nx.empty_graph(1, create_using=nx.DiGraph) + assert {0: 1} == nx.degree_centrality(G) + assert {0: 1} == nx.out_degree_centrality(G) + assert {0: 1} == nx.in_degree_centrality(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_dispersion.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_dispersion.py new file mode 100644 index 0000000000000000000000000000000000000000..05de1c43659a44f2dbf45368bf2ee552dd61dd78 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_dispersion.py @@ -0,0 +1,73 @@ +import networkx as nx + + +def small_ego_G(): + """The sample network from https://arxiv.org/pdf/1310.6753v1.pdf""" + edges = [ + ("a", "b"), + ("a", "c"), + ("b", "c"), + ("b", "d"), + ("b", "e"), + ("b", "f"), + ("c", "d"), + ("c", "f"), + ("c", "h"), + ("d", "f"), + ("e", "f"), + ("f", "h"), + ("h", "j"), + ("h", "k"), + ("i", "j"), + ("i", "k"), + ("j", "k"), + ("u", "a"), + ("u", "b"), + ("u", "c"), + ("u", "d"), + ("u", "e"), + ("u", "f"), + ("u", "g"), + ("u", "h"), + ("u", "i"), + ("u", "j"), + ("u", "k"), + ] + G = nx.Graph() + G.add_edges_from(edges) + + return G + + +class TestDispersion: + def test_article(self): + """our algorithm matches article's""" + G = small_ego_G() + disp_uh = nx.dispersion(G, "u", "h", normalized=False) + disp_ub = nx.dispersion(G, "u", "b", normalized=False) + assert disp_uh == 4 + assert disp_ub == 1 + + def test_results_length(self): + """there is a result for every node""" + G = small_ego_G() + disp = nx.dispersion(G) + disp_Gu = nx.dispersion(G, "u") + disp_uv = nx.dispersion(G, "u", "h") + assert len(disp) == len(G) + assert len(disp_Gu) == len(G) - 1 + assert isinstance(disp_uv, float) + + def test_dispersion_v_only(self): + G = small_ego_G() + disp_G_h = nx.dispersion(G, v="h", normalized=False) + disp_G_h_normalized = nx.dispersion(G, v="h", normalized=True) + assert disp_G_h == {"c": 0, "f": 0, "j": 0, "k": 0, "u": 4} + assert disp_G_h_normalized == {"c": 0.0, "f": 0.0, "j": 0.0, "k": 0.0, "u": 1.0} + + def test_impossible_things(self): + G = nx.karate_club_graph() + disp = nx.dispersion(G) + for u in disp: + for v in disp[u]: + assert disp[u][v] >= 0 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..7322aae10b99188497729edeb283ebc7ba50f077 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py @@ -0,0 +1,186 @@ +import math + +import pytest + +import networkx as nx + +np = pytest.importorskip("numpy") +pytest.importorskip("scipy") + + +class TestEigenvectorCentrality: + def test_K5(self): + """Eigenvector centrality: K5""" + G = nx.complete_graph(5) + b = nx.eigenvector_centrality(G) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + nstart = {n: 1 for n in G} + b = nx.eigenvector_centrality(G, nstart=nstart) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + b = nx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_P3(self): + """Eigenvector centrality: P3""" + G = nx.path_graph(3) + b_answer = {0: 0.5, 1: 0.7071, 2: 0.5} + b = nx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + b = nx.eigenvector_centrality(G) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_P3_unweighted(self): + """Eigenvector centrality: P3""" + G = nx.path_graph(3) + b_answer = {0: 0.5, 1: 0.7071, 2: 0.5} + b = nx.eigenvector_centrality_numpy(G, weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_maxiter(self): + with pytest.raises(nx.PowerIterationFailedConvergence): + G = nx.path_graph(3) + nx.eigenvector_centrality(G, max_iter=0) + + +class TestEigenvectorCentralityDirected: + @classmethod + def setup_class(cls): + G = nx.DiGraph() + + edges = [ + (1, 2), + (1, 3), + (2, 4), + (3, 2), + (3, 5), + (4, 2), + (4, 5), + (4, 6), + (5, 6), + (5, 7), + (5, 8), + (6, 8), + (7, 1), + (7, 5), + (7, 8), + (8, 6), + (8, 7), + ] + + G.add_edges_from(edges, weight=2.0) + cls.G = G.reverse() + cls.G.evc = [ + 0.25368793, + 0.19576478, + 0.32817092, + 0.40430835, + 0.48199885, + 0.15724483, + 0.51346196, + 0.32475403, + ] + + H = nx.DiGraph() + + edges = [ + (1, 2), + (1, 3), + (2, 4), + (3, 2), + (3, 5), + (4, 2), + (4, 5), + (4, 6), + (5, 6), + (5, 7), + (5, 8), + (6, 8), + (7, 1), + (7, 5), + (7, 8), + (8, 6), + (8, 7), + ] + + G.add_edges_from(edges) + cls.H = G.reverse() + cls.H.evc = [ + 0.25368793, + 0.19576478, + 0.32817092, + 0.40430835, + 0.48199885, + 0.15724483, + 0.51346196, + 0.32475403, + ] + + def test_eigenvector_centrality_weighted(self): + G = self.G + p = nx.eigenvector_centrality(G) + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-4) + + def test_eigenvector_centrality_weighted_numpy(self): + G = self.G + p = nx.eigenvector_centrality_numpy(G) + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-7) + + def test_eigenvector_centrality_unweighted(self): + G = self.H + p = nx.eigenvector_centrality(G) + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-4) + + def test_eigenvector_centrality_unweighted_numpy(self): + G = self.H + p = nx.eigenvector_centrality_numpy(G) + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-7) + + +class TestEigenvectorCentralityExceptions: + def test_multigraph(self): + with pytest.raises(nx.NetworkXException): + nx.eigenvector_centrality(nx.MultiGraph()) + + def test_multigraph_numpy(self): + with pytest.raises(nx.NetworkXException): + nx.eigenvector_centrality_numpy(nx.MultiGraph()) + + def test_null(self): + with pytest.raises(nx.NetworkXException): + nx.eigenvector_centrality(nx.Graph()) + + def test_null_numpy(self): + with pytest.raises(nx.NetworkXException): + nx.eigenvector_centrality_numpy(nx.Graph()) + + @pytest.mark.parametrize( + "G", + [ + nx.empty_graph(3), + nx.DiGraph([(0, 1), (1, 2)]), + ], + ) + def test_disconnected_numpy(self, G): + msg = "does not give consistent results for disconnected" + with pytest.raises(nx.AmbiguousSolution, match=msg): + nx.eigenvector_centrality_numpy(G) + + def test_zero_nstart(self): + G = nx.Graph([(1, 2), (1, 3), (2, 3)]) + with pytest.raises( + nx.NetworkXException, match="initial vector cannot have all zero values" + ): + nx.eigenvector_centrality(G, nstart={v: 0 for v in G}) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_group.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_group.py new file mode 100644 index 0000000000000000000000000000000000000000..82343f28702382c18676fc776511bd7efdf22a78 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_group.py @@ -0,0 +1,277 @@ +""" +Tests for Group Centrality Measures +""" + +import pytest + +import networkx as nx + + +class TestGroupBetweennessCentrality: + def test_group_betweenness_single_node(self): + """ + Group betweenness centrality for single node group + """ + G = nx.path_graph(5) + C = [1] + b = nx.group_betweenness_centrality( + G, C, weight=None, normalized=False, endpoints=False + ) + b_answer = 3.0 + assert b == b_answer + + def test_group_betweenness_with_endpoints(self): + """ + Group betweenness centrality for single node group + """ + G = nx.path_graph(5) + C = [1] + b = nx.group_betweenness_centrality( + G, C, weight=None, normalized=False, endpoints=True + ) + b_answer = 7.0 + assert b == b_answer + + def test_group_betweenness_normalized(self): + """ + Group betweenness centrality for group with more than + 1 node and normalized + """ + G = nx.path_graph(5) + C = [1, 3] + b = nx.group_betweenness_centrality( + G, C, weight=None, normalized=True, endpoints=False + ) + b_answer = 1.0 + assert b == b_answer + + def test_two_group_betweenness_value_zero(self): + """ + Group betweenness centrality value of 0 + """ + G = nx.cycle_graph(7) + C = [[0, 1, 6], [0, 1, 5]] + b = nx.group_betweenness_centrality(G, C, weight=None, normalized=False) + b_answer = [0.0, 3.0] + assert b == b_answer + + def test_group_betweenness_value_zero(self): + """ + Group betweenness centrality value of 0 + """ + G = nx.cycle_graph(6) + C = [0, 1, 5] + b = nx.group_betweenness_centrality(G, C, weight=None, normalized=False) + b_answer = 0.0 + assert b == b_answer + + def test_group_betweenness_disconnected_graph(self): + """ + Group betweenness centrality in a disconnected graph + """ + G = nx.path_graph(5) + G.remove_edge(0, 1) + C = [1] + b = nx.group_betweenness_centrality(G, C, weight=None, normalized=False) + b_answer = 0.0 + assert b == b_answer + + def test_group_betweenness_node_not_in_graph(self): + """ + Node(s) in C not in graph, raises NodeNotFound exception + """ + with pytest.raises(nx.NodeNotFound): + nx.group_betweenness_centrality(nx.path_graph(5), [4, 7, 8]) + + def test_group_betweenness_directed_weighted(self): + """ + Group betweenness centrality in a directed and weighted graph + """ + G = nx.DiGraph() + G.add_edge(1, 0, weight=1) + G.add_edge(0, 2, weight=2) + G.add_edge(1, 2, weight=3) + G.add_edge(3, 1, weight=4) + G.add_edge(2, 3, weight=1) + G.add_edge(4, 3, weight=6) + G.add_edge(2, 4, weight=7) + C = [1, 2] + b = nx.group_betweenness_centrality(G, C, weight="weight", normalized=False) + b_answer = 5.0 + assert b == b_answer + + +class TestProminentGroup: + np = pytest.importorskip("numpy") + pd = pytest.importorskip("pandas") + + def test_prominent_group_single_node(self): + """ + Prominent group for single node + """ + G = nx.path_graph(5) + k = 1 + b, g = nx.prominent_group(G, k, normalized=False, endpoints=False) + b_answer, g_answer = 4.0, [2] + assert b == b_answer and g == g_answer + + def test_prominent_group_with_c(self): + """ + Prominent group without some nodes + """ + G = nx.path_graph(5) + k = 1 + b, g = nx.prominent_group(G, k, normalized=False, C=[2]) + b_answer, g_answer = 3.0, [1] + assert b == b_answer and g == g_answer + + def test_prominent_group_normalized_endpoints(self): + """ + Prominent group with normalized result, with endpoints + """ + G = nx.cycle_graph(7) + k = 2 + b, g = nx.prominent_group(G, k, normalized=True, endpoints=True) + b_answer, g_answer = 1.7, [2, 5] + assert b == b_answer and g == g_answer + + def test_prominent_group_disconnected_graph(self): + """ + Prominent group of disconnected graph + """ + G = nx.path_graph(6) + G.remove_edge(0, 1) + k = 1 + b, g = nx.prominent_group(G, k, weight=None, normalized=False) + b_answer, g_answer = 4.0, [3] + assert b == b_answer and g == g_answer + + def test_prominent_group_node_not_in_graph(self): + """ + Node(s) in C not in graph, raises NodeNotFound exception + """ + with pytest.raises(nx.NodeNotFound): + nx.prominent_group(nx.path_graph(5), 1, C=[10]) + + def test_group_betweenness_directed_weighted(self): + """ + Group betweenness centrality in a directed and weighted graph + """ + G = nx.DiGraph() + G.add_edge(1, 0, weight=1) + G.add_edge(0, 2, weight=2) + G.add_edge(1, 2, weight=3) + G.add_edge(3, 1, weight=4) + G.add_edge(2, 3, weight=1) + G.add_edge(4, 3, weight=6) + G.add_edge(2, 4, weight=7) + k = 2 + b, g = nx.prominent_group(G, k, weight="weight", normalized=False) + b_answer, g_answer = 5.0, [1, 2] + assert b == b_answer and g == g_answer + + def test_prominent_group_greedy_algorithm(self): + """ + Group betweenness centrality in a greedy algorithm + """ + G = nx.cycle_graph(7) + k = 2 + b, g = nx.prominent_group(G, k, normalized=True, endpoints=True, greedy=True) + b_answer, g_answer = 1.7, [6, 3] + assert b == b_answer and g == g_answer + + +class TestGroupClosenessCentrality: + def test_group_closeness_single_node(self): + """ + Group closeness centrality for a single node group + """ + G = nx.path_graph(5) + c = nx.group_closeness_centrality(G, [1]) + c_answer = nx.closeness_centrality(G, 1) + assert c == c_answer + + def test_group_closeness_disconnected(self): + """ + Group closeness centrality for a disconnected graph + """ + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4]) + c = nx.group_closeness_centrality(G, [1, 2]) + c_answer = 0 + assert c == c_answer + + def test_group_closeness_multiple_node(self): + """ + Group closeness centrality for a group with more than + 1 node + """ + G = nx.path_graph(4) + c = nx.group_closeness_centrality(G, [1, 2]) + c_answer = 1 + assert c == c_answer + + def test_group_closeness_node_not_in_graph(self): + """ + Node(s) in S not in graph, raises NodeNotFound exception + """ + with pytest.raises(nx.NodeNotFound): + nx.group_closeness_centrality(nx.path_graph(5), [6, 7, 8]) + + +class TestGroupDegreeCentrality: + def test_group_degree_centrality_single_node(self): + """ + Group degree centrality for a single node group + """ + G = nx.path_graph(4) + d = nx.group_degree_centrality(G, [1]) + d_answer = nx.degree_centrality(G)[1] + assert d == d_answer + + def test_group_degree_centrality_multiple_node(self): + """ + Group degree centrality for group with more than + 1 node + """ + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) + G.add_edges_from( + [(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)] + ) + d = nx.group_degree_centrality(G, [1, 2]) + d_answer = 1 + assert d == d_answer + + def test_group_in_degree_centrality(self): + """ + Group in-degree centrality in a DiGraph + """ + G = nx.DiGraph() + G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) + G.add_edges_from( + [(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)] + ) + d = nx.group_in_degree_centrality(G, [1, 2]) + d_answer = 0 + assert d == d_answer + + def test_group_out_degree_centrality(self): + """ + Group out-degree centrality in a DiGraph + """ + G = nx.DiGraph() + G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) + G.add_edges_from( + [(1, 2), (1, 3), (1, 6), (1, 7), (1, 8), (2, 3), (2, 4), (2, 5)] + ) + d = nx.group_out_degree_centrality(G, [1, 2]) + d_answer = 1 + assert d == d_answer + + def test_group_degree_centrality_node_not_in_graph(self): + """ + Node(s) in S not in graph, raises NetworkXError + """ + with pytest.raises(nx.NetworkXError): + nx.group_degree_centrality(nx.path_graph(5), [6, 7, 8]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_harmonic_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_harmonic_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..4b3dc4ac356701eb562a3179a69023ad83a8d74e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_harmonic_centrality.py @@ -0,0 +1,122 @@ +""" +Tests for degree centrality. +""" + +import pytest + +import networkx as nx +from networkx.algorithms.centrality import harmonic_centrality + + +class TestClosenessCentrality: + @classmethod + def setup_class(cls): + cls.P3 = nx.path_graph(3) + cls.P4 = nx.path_graph(4) + cls.K5 = nx.complete_graph(5) + + cls.C4 = nx.cycle_graph(4) + cls.C4_directed = nx.cycle_graph(4, create_using=nx.DiGraph) + + cls.C5 = nx.cycle_graph(5) + + cls.T = nx.balanced_tree(r=2, h=2) + + cls.Gb = nx.DiGraph() + cls.Gb.add_edges_from([(0, 1), (0, 2), (0, 4), (2, 1), (2, 3), (4, 3)]) + + def test_p3_harmonic(self): + c = harmonic_centrality(self.P3) + d = {0: 1.5, 1: 2, 2: 1.5} + for n in sorted(self.P3): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_p4_harmonic(self): + c = harmonic_centrality(self.P4) + d = {0: 1.8333333, 1: 2.5, 2: 2.5, 3: 1.8333333} + for n in sorted(self.P4): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_clique_complete(self): + c = harmonic_centrality(self.K5) + d = {0: 4, 1: 4, 2: 4, 3: 4, 4: 4} + for n in sorted(self.P3): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_cycle_C4(self): + c = harmonic_centrality(self.C4) + d = {0: 2.5, 1: 2.5, 2: 2.5, 3: 2.5} + for n in sorted(self.C4): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_cycle_C5(self): + c = harmonic_centrality(self.C5) + d = {0: 3, 1: 3, 2: 3, 3: 3, 4: 3, 5: 4} + for n in sorted(self.C5): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_bal_tree(self): + c = harmonic_centrality(self.T) + d = {0: 4.0, 1: 4.1666, 2: 4.1666, 3: 2.8333, 4: 2.8333, 5: 2.8333, 6: 2.8333} + for n in sorted(self.T): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_exampleGraph(self): + c = harmonic_centrality(self.Gb) + d = {0: 0, 1: 2, 2: 1, 3: 2.5, 4: 1} + for n in sorted(self.Gb): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_weighted_harmonic(self): + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [ + ("a", "b", 10), + ("d", "c", 5), + ("a", "c", 1), + ("e", "f", 2), + ("f", "c", 1), + ("a", "f", 3), + ] + ) + c = harmonic_centrality(XG, distance="weight") + d = {"a": 0, "b": 0.1, "c": 2.533, "d": 0, "e": 0, "f": 0.83333} + for n in sorted(XG): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_empty(self): + G = nx.DiGraph() + c = harmonic_centrality(G, distance="weight") + d = {} + assert c == d + + def test_singleton(self): + G = nx.DiGraph() + G.add_node(0) + c = harmonic_centrality(G, distance="weight") + d = {0: 0} + assert c == d + + def test_cycle_c4_directed(self): + c = harmonic_centrality(self.C4_directed, nbunch=[0, 1], sources=[1, 2]) + d = {0: 0.833, 1: 0.333} + for n in [0, 1]: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_cycle_c4_directed_subset(self): + c = harmonic_centrality(self.C4_directed, nbunch=[0, 1]) + d = 1.833 + for n in [0, 1]: + assert c[n] == pytest.approx(d, abs=1e-3) + + def test_p3_harmonic_subset(self): + c = harmonic_centrality(self.P3, sources=[0, 1]) + d = {0: 1, 1: 1, 2: 1.5} + for n in self.P3: + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_p4_harmonic_subset(self): + c = harmonic_centrality(self.P4, nbunch=[2, 3], sources=[0, 1]) + d = {2: 1.5, 3: 0.8333333} + for n in [2, 3]: + assert c[n] == pytest.approx(d[n], abs=1e-3) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_katz_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_katz_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..0927f00bc5c31ad1134dae0c8f59367baed67bb6 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_katz_centrality.py @@ -0,0 +1,345 @@ +import math + +import pytest + +import networkx as nx + + +class TestKatzCentrality: + def test_K5(self): + """Katz centrality: K5""" + G = nx.complete_graph(5) + alpha = 0.1 + b = nx.katz_centrality(G, alpha) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + nstart = {n: 1 for n in G} + b = nx.katz_centrality(G, alpha, nstart=nstart) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + + def test_P3(self): + """Katz centrality: P3""" + alpha = 0.1 + G = nx.path_graph(3) + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + b = nx.katz_centrality(G, alpha) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_maxiter(self): + with pytest.raises(nx.PowerIterationFailedConvergence): + nx.katz_centrality(nx.path_graph(3), 0.1, max_iter=0) + + def test_beta_as_scalar(self): + alpha = 0.1 + beta = 0.1 + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + G = nx.path_graph(3) + b = nx.katz_centrality(G, alpha, beta) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_beta_as_dict(self): + alpha = 0.1 + beta = {0: 1.0, 1: 1.0, 2: 1.0} + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + G = nx.path_graph(3) + b = nx.katz_centrality(G, alpha, beta) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_multiple_alpha(self): + alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + for alpha in alpha_list: + b_answer = { + 0.1: { + 0: 0.5598852584152165, + 1: 0.6107839182711449, + 2: 0.5598852584152162, + }, + 0.2: { + 0: 0.5454545454545454, + 1: 0.6363636363636365, + 2: 0.5454545454545454, + }, + 0.3: { + 0: 0.5333964609104419, + 1: 0.6564879518897746, + 2: 0.5333964609104419, + }, + 0.4: { + 0: 0.5232045649263551, + 1: 0.6726915834767423, + 2: 0.5232045649263551, + }, + 0.5: { + 0: 0.5144957746691622, + 1: 0.6859943117075809, + 2: 0.5144957746691622, + }, + 0.6: { + 0: 0.5069794004195823, + 1: 0.6970966755769258, + 2: 0.5069794004195823, + }, + } + G = nx.path_graph(3) + b = nx.katz_centrality(G, alpha) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[alpha][n], abs=1e-4) + + def test_multigraph(self): + with pytest.raises(nx.NetworkXException): + nx.katz_centrality(nx.MultiGraph(), 0.1) + + def test_empty(self): + e = nx.katz_centrality(nx.Graph(), 0.1) + assert e == {} + + def test_bad_beta(self): + with pytest.raises(nx.NetworkXException): + G = nx.Graph([(0, 1)]) + beta = {0: 77} + nx.katz_centrality(G, 0.1, beta=beta) + + def test_bad_beta_number(self): + with pytest.raises(nx.NetworkXException): + G = nx.Graph([(0, 1)]) + nx.katz_centrality(G, 0.1, beta="foo") + + +class TestKatzCentralityNumpy: + @classmethod + def setup_class(cls): + global np + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + def test_K5(self): + """Katz centrality: K5""" + G = nx.complete_graph(5) + alpha = 0.1 + b = nx.katz_centrality(G, alpha) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + b = nx.eigenvector_centrality_numpy(G) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_P3(self): + """Katz centrality: P3""" + alpha = 0.1 + G = nx.path_graph(3) + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + b = nx.katz_centrality_numpy(G, alpha) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_beta_as_scalar(self): + alpha = 0.1 + beta = 0.1 + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + G = nx.path_graph(3) + b = nx.katz_centrality_numpy(G, alpha, beta) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_beta_as_dict(self): + alpha = 0.1 + beta = {0: 1.0, 1: 1.0, 2: 1.0} + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + G = nx.path_graph(3) + b = nx.katz_centrality_numpy(G, alpha, beta) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + def test_multiple_alpha(self): + alpha_list = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6] + for alpha in alpha_list: + b_answer = { + 0.1: { + 0: 0.5598852584152165, + 1: 0.6107839182711449, + 2: 0.5598852584152162, + }, + 0.2: { + 0: 0.5454545454545454, + 1: 0.6363636363636365, + 2: 0.5454545454545454, + }, + 0.3: { + 0: 0.5333964609104419, + 1: 0.6564879518897746, + 2: 0.5333964609104419, + }, + 0.4: { + 0: 0.5232045649263551, + 1: 0.6726915834767423, + 2: 0.5232045649263551, + }, + 0.5: { + 0: 0.5144957746691622, + 1: 0.6859943117075809, + 2: 0.5144957746691622, + }, + 0.6: { + 0: 0.5069794004195823, + 1: 0.6970966755769258, + 2: 0.5069794004195823, + }, + } + G = nx.path_graph(3) + b = nx.katz_centrality_numpy(G, alpha) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[alpha][n], abs=1e-4) + + def test_multigraph(self): + with pytest.raises(nx.NetworkXException): + nx.katz_centrality(nx.MultiGraph(), 0.1) + + def test_empty(self): + e = nx.katz_centrality(nx.Graph(), 0.1) + assert e == {} + + def test_bad_beta(self): + with pytest.raises(nx.NetworkXException): + G = nx.Graph([(0, 1)]) + beta = {0: 77} + nx.katz_centrality_numpy(G, 0.1, beta=beta) + + def test_bad_beta_numbe(self): + with pytest.raises(nx.NetworkXException): + G = nx.Graph([(0, 1)]) + nx.katz_centrality_numpy(G, 0.1, beta="foo") + + def test_K5_unweighted(self): + """Katz centrality: K5""" + G = nx.complete_graph(5) + alpha = 0.1 + b = nx.katz_centrality(G, alpha, weight=None) + v = math.sqrt(1 / 5.0) + b_answer = dict.fromkeys(G, v) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-7) + b = nx.eigenvector_centrality_numpy(G, weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-3) + + def test_P3_unweighted(self): + """Katz centrality: P3""" + alpha = 0.1 + G = nx.path_graph(3) + b_answer = {0: 0.5598852584152165, 1: 0.6107839182711449, 2: 0.5598852584152162} + b = nx.katz_centrality_numpy(G, alpha, weight=None) + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-4) + + +class TestKatzCentralityDirected: + @classmethod + def setup_class(cls): + G = nx.DiGraph() + edges = [ + (1, 2), + (1, 3), + (2, 4), + (3, 2), + (3, 5), + (4, 2), + (4, 5), + (4, 6), + (5, 6), + (5, 7), + (5, 8), + (6, 8), + (7, 1), + (7, 5), + (7, 8), + (8, 6), + (8, 7), + ] + G.add_edges_from(edges, weight=2.0) + cls.G = G.reverse() + cls.G.alpha = 0.1 + cls.G.evc = [ + 0.3289589783189635, + 0.2832077296243516, + 0.3425906003685471, + 0.3970420865198392, + 0.41074871061646284, + 0.272257430756461, + 0.4201989685435462, + 0.34229059218038554, + ] + + H = nx.DiGraph(edges) + cls.H = G.reverse() + cls.H.alpha = 0.1 + cls.H.evc = [ + 0.3289589783189635, + 0.2832077296243516, + 0.3425906003685471, + 0.3970420865198392, + 0.41074871061646284, + 0.272257430756461, + 0.4201989685435462, + 0.34229059218038554, + ] + + def test_katz_centrality_weighted(self): + G = self.G + alpha = self.G.alpha + p = nx.katz_centrality(G, alpha, weight="weight") + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-7) + + def test_katz_centrality_unweighted(self): + H = self.H + alpha = self.H.alpha + p = nx.katz_centrality(H, alpha, weight="weight") + for a, b in zip(list(p.values()), self.H.evc): + assert a == pytest.approx(b, abs=1e-7) + + +class TestKatzCentralityDirectedNumpy(TestKatzCentralityDirected): + @classmethod + def setup_class(cls): + global np + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + super().setup_class() + + def test_katz_centrality_weighted(self): + G = self.G + alpha = self.G.alpha + p = nx.katz_centrality_numpy(G, alpha, weight="weight") + for a, b in zip(list(p.values()), self.G.evc): + assert a == pytest.approx(b, abs=1e-7) + + def test_katz_centrality_unweighted(self): + H = self.H + alpha = self.H.alpha + p = nx.katz_centrality_numpy(H, alpha, weight="weight") + for a, b in zip(list(p.values()), self.H.evc): + assert a == pytest.approx(b, abs=1e-7) + + +class TestKatzEigenvectorVKatz: + @classmethod + def setup_class(cls): + global np + np = pytest.importorskip("numpy") + pytest.importorskip("scipy") + + def test_eigenvector_v_katz_random(self): + G = nx.gnp_random_graph(10, 0.5, seed=1234) + l = max(np.linalg.eigvals(nx.adjacency_matrix(G).todense())) + e = nx.eigenvector_centrality_numpy(G) + k = nx.katz_centrality_numpy(G, 1.0 / l) + for n in G: + assert e[n] == pytest.approx(k[n], abs=1e-7) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_laplacian_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_laplacian_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..c2a2915d8f8a1d60e73888f3825c0e8ef9044ed0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_laplacian_centrality.py @@ -0,0 +1,220 @@ +import pytest + +import networkx as nx + +np = pytest.importorskip("numpy") +sp = pytest.importorskip("scipy") + + +def test_laplacian_centrality_null_graph(): + G = nx.Graph() + with pytest.raises(nx.NetworkXPointlessConcept): + d = nx.laplacian_centrality(G, normalized=False) + + +def test_laplacian_centrality_single_node(): + """See gh-6571""" + G = nx.empty_graph(1) + assert nx.laplacian_centrality(G, normalized=False) == {0: 0} + with pytest.raises(ZeroDivisionError): + nx.laplacian_centrality(G, normalized=True) + + +def test_laplacian_centrality_unconnected_nodes(): + """laplacian_centrality on a unconnected node graph should return 0 + + For graphs without edges, the Laplacian energy is 0 and is unchanged with + node removal, so:: + + LC(v) = LE(G) - LE(G - v) = 0 - 0 = 0 + """ + G = nx.empty_graph(3) + assert nx.laplacian_centrality(G, normalized=False) == {0: 0, 1: 0, 2: 0} + + +def test_laplacian_centrality_empty_graph(): + G = nx.empty_graph(3) + with pytest.raises(ZeroDivisionError): + d = nx.laplacian_centrality(G, normalized=True) + + +def test_laplacian_centrality_E(): + E = nx.Graph() + E.add_weighted_edges_from( + [(0, 1, 4), (4, 5, 1), (0, 2, 2), (2, 1, 1), (1, 3, 2), (1, 4, 2)] + ) + d = nx.laplacian_centrality(E) + exact = { + 0: 0.700000, + 1: 0.900000, + 2: 0.280000, + 3: 0.220000, + 4: 0.260000, + 5: 0.040000, + } + + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + # Check not normalized + full_energy = 200 + dnn = nx.laplacian_centrality(E, normalized=False) + for n, dc in dnn.items(): + assert exact[n] * full_energy == pytest.approx(dc, abs=1e-7) + + # Check unweighted not-normalized version + duw_nn = nx.laplacian_centrality(E, normalized=False, weight=None) + exact_uw_nn = { + 0: 18, + 1: 34, + 2: 18, + 3: 10, + 4: 16, + 5: 6, + } + for n, dc in duw_nn.items(): + assert exact_uw_nn[n] == pytest.approx(dc, abs=1e-7) + + # Check unweighted version + duw = nx.laplacian_centrality(E, weight=None) + full_energy = 42 + for n, dc in duw.items(): + assert exact_uw_nn[n] / full_energy == pytest.approx(dc, abs=1e-7) + + +def test_laplacian_centrality_KC(): + KC = nx.karate_club_graph() + d = nx.laplacian_centrality(KC) + exact = { + 0: 0.2543593, + 1: 0.1724524, + 2: 0.2166053, + 3: 0.0964646, + 4: 0.0350344, + 5: 0.0571109, + 6: 0.0540713, + 7: 0.0788674, + 8: 0.1222204, + 9: 0.0217565, + 10: 0.0308751, + 11: 0.0215965, + 12: 0.0174372, + 13: 0.118861, + 14: 0.0366341, + 15: 0.0548712, + 16: 0.0172772, + 17: 0.0191969, + 18: 0.0225564, + 19: 0.0331147, + 20: 0.0279955, + 21: 0.0246361, + 22: 0.0382339, + 23: 0.1294193, + 24: 0.0227164, + 25: 0.0644697, + 26: 0.0281555, + 27: 0.075188, + 28: 0.0364742, + 29: 0.0707087, + 30: 0.0708687, + 31: 0.131019, + 32: 0.2370821, + 33: 0.3066709, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + # Check not normalized + full_energy = 12502 + dnn = nx.laplacian_centrality(KC, normalized=False) + for n, dc in dnn.items(): + assert exact[n] * full_energy == pytest.approx(dc, abs=1e-3) + + +def test_laplacian_centrality_K(): + K = nx.krackhardt_kite_graph() + d = nx.laplacian_centrality(K) + exact = { + 0: 0.3010753, + 1: 0.3010753, + 2: 0.2258065, + 3: 0.483871, + 4: 0.2258065, + 5: 0.3870968, + 6: 0.3870968, + 7: 0.1935484, + 8: 0.0752688, + 9: 0.0322581, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + # Check not normalized + full_energy = 186 + dnn = nx.laplacian_centrality(K, normalized=False) + for n, dc in dnn.items(): + assert exact[n] * full_energy == pytest.approx(dc, abs=1e-3) + + +def test_laplacian_centrality_P3(): + P3 = nx.path_graph(3) + d = nx.laplacian_centrality(P3) + exact = {0: 0.6, 1: 1.0, 2: 0.6} + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + +def test_laplacian_centrality_K5(): + K5 = nx.complete_graph(5) + d = nx.laplacian_centrality(K5) + exact = {0: 0.52, 1: 0.52, 2: 0.52, 3: 0.52, 4: 0.52} + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + +def test_laplacian_centrality_FF(): + FF = nx.florentine_families_graph() + d = nx.laplacian_centrality(FF) + exact = { + "Acciaiuoli": 0.0804598, + "Medici": 0.4022989, + "Castellani": 0.1724138, + "Peruzzi": 0.183908, + "Strozzi": 0.2528736, + "Barbadori": 0.137931, + "Ridolfi": 0.2183908, + "Tornabuoni": 0.2183908, + "Albizzi": 0.1954023, + "Salviati": 0.1149425, + "Pazzi": 0.0344828, + "Bischeri": 0.1954023, + "Guadagni": 0.2298851, + "Ginori": 0.045977, + "Lamberteschi": 0.0574713, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + +def test_laplacian_centrality_DG(): + DG = nx.DiGraph([(0, 5), (1, 5), (2, 5), (3, 5), (4, 5), (5, 6), (5, 7), (5, 8)]) + d = nx.laplacian_centrality(DG) + exact = { + 0: 0.2123352, + 5: 0.515391, + 1: 0.2123352, + 2: 0.2123352, + 3: 0.2123352, + 4: 0.2123352, + 6: 0.2952031, + 7: 0.2952031, + 8: 0.2952031, + } + for n, dc in d.items(): + assert exact[n] == pytest.approx(dc, abs=1e-7) + + # Check not normalized + full_energy = 9.50704 + dnn = nx.laplacian_centrality(DG, normalized=False) + for n, dc in dnn.items(): + assert exact[n] * full_energy == pytest.approx(dc, abs=1e-4) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_load_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_load_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..bf096039cd76542cc4c963ab896ee8fc4b295224 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_load_centrality.py @@ -0,0 +1,344 @@ +import pytest + +import networkx as nx + + +class TestLoadCentrality: + @classmethod + def setup_class(cls): + G = nx.Graph() + G.add_edge(0, 1, weight=3) + G.add_edge(0, 2, weight=2) + G.add_edge(0, 3, weight=6) + G.add_edge(0, 4, weight=4) + G.add_edge(1, 3, weight=5) + G.add_edge(1, 5, weight=5) + G.add_edge(2, 4, weight=1) + G.add_edge(3, 4, weight=2) + G.add_edge(3, 5, weight=1) + G.add_edge(4, 5, weight=4) + cls.G = G + cls.exact_weighted = {0: 4.0, 1: 0.0, 2: 8.0, 3: 6.0, 4: 8.0, 5: 0.0} + cls.K = nx.krackhardt_kite_graph() + cls.P3 = nx.path_graph(3) + cls.P4 = nx.path_graph(4) + cls.K5 = nx.complete_graph(5) + cls.P2 = nx.path_graph(2) + + cls.C4 = nx.cycle_graph(4) + cls.T = nx.balanced_tree(r=2, h=2) + cls.Gb = nx.Graph() + cls.Gb.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)]) + cls.F = nx.florentine_families_graph() + cls.LM = nx.les_miserables_graph() + cls.D = nx.cycle_graph(3, create_using=nx.DiGraph()) + cls.D.add_edges_from([(3, 0), (4, 3)]) + + def test_not_strongly_connected(self): + b = nx.load_centrality(self.D) + result = {0: 5.0 / 12, 1: 1.0 / 4, 2: 1.0 / 12, 3: 1.0 / 4, 4: 0.000} + for n in sorted(self.D): + assert result[n] == pytest.approx(b[n], abs=1e-3) + assert result[n] == pytest.approx(nx.load_centrality(self.D, n), abs=1e-3) + + def test_P2_normalized_load(self): + G = self.P2 + c = nx.load_centrality(G, normalized=True) + d = {0: 0.000, 1: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_weighted_load(self): + b = nx.load_centrality(self.G, weight="weight", normalized=False) + for n in sorted(self.G): + assert b[n] == self.exact_weighted[n] + + def test_k5_load(self): + G = self.K5 + c = nx.load_centrality(G) + d = {0: 0.000, 1: 0.000, 2: 0.000, 3: 0.000, 4: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_p3_load(self): + G = self.P3 + c = nx.load_centrality(G) + d = {0: 0.000, 1: 1.000, 2: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + c = nx.load_centrality(G, v=1) + assert c == pytest.approx(1.0, abs=1e-7) + c = nx.load_centrality(G, v=1, normalized=True) + assert c == pytest.approx(1.0, abs=1e-7) + + def test_p2_load(self): + G = nx.path_graph(2) + c = nx.load_centrality(G) + d = {0: 0.000, 1: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_krackhardt_load(self): + G = self.K + c = nx.load_centrality(G) + d = { + 0: 0.023, + 1: 0.023, + 2: 0.000, + 3: 0.102, + 4: 0.000, + 5: 0.231, + 6: 0.231, + 7: 0.389, + 8: 0.222, + 9: 0.000, + } + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_florentine_families_load(self): + G = self.F + c = nx.load_centrality(G) + d = { + "Acciaiuoli": 0.000, + "Albizzi": 0.211, + "Barbadori": 0.093, + "Bischeri": 0.104, + "Castellani": 0.055, + "Ginori": 0.000, + "Guadagni": 0.251, + "Lamberteschi": 0.000, + "Medici": 0.522, + "Pazzi": 0.000, + "Peruzzi": 0.022, + "Ridolfi": 0.117, + "Salviati": 0.143, + "Strozzi": 0.106, + "Tornabuoni": 0.090, + } + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_les_miserables_load(self): + G = self.LM + c = nx.load_centrality(G) + d = { + "Napoleon": 0.000, + "Myriel": 0.177, + "MlleBaptistine": 0.000, + "MmeMagloire": 0.000, + "CountessDeLo": 0.000, + "Geborand": 0.000, + "Champtercier": 0.000, + "Cravatte": 0.000, + "Count": 0.000, + "OldMan": 0.000, + "Valjean": 0.567, + "Labarre": 0.000, + "Marguerite": 0.000, + "MmeDeR": 0.000, + "Isabeau": 0.000, + "Gervais": 0.000, + "Listolier": 0.000, + "Tholomyes": 0.043, + "Fameuil": 0.000, + "Blacheville": 0.000, + "Favourite": 0.000, + "Dahlia": 0.000, + "Zephine": 0.000, + "Fantine": 0.128, + "MmeThenardier": 0.029, + "Thenardier": 0.075, + "Cosette": 0.024, + "Javert": 0.054, + "Fauchelevent": 0.026, + "Bamatabois": 0.008, + "Perpetue": 0.000, + "Simplice": 0.009, + "Scaufflaire": 0.000, + "Woman1": 0.000, + "Judge": 0.000, + "Champmathieu": 0.000, + "Brevet": 0.000, + "Chenildieu": 0.000, + "Cochepaille": 0.000, + "Pontmercy": 0.007, + "Boulatruelle": 0.000, + "Eponine": 0.012, + "Anzelma": 0.000, + "Woman2": 0.000, + "MotherInnocent": 0.000, + "Gribier": 0.000, + "MmeBurgon": 0.026, + "Jondrette": 0.000, + "Gavroche": 0.164, + "Gillenormand": 0.021, + "Magnon": 0.000, + "MlleGillenormand": 0.047, + "MmePontmercy": 0.000, + "MlleVaubois": 0.000, + "LtGillenormand": 0.000, + "Marius": 0.133, + "BaronessT": 0.000, + "Mabeuf": 0.028, + "Enjolras": 0.041, + "Combeferre": 0.001, + "Prouvaire": 0.000, + "Feuilly": 0.001, + "Courfeyrac": 0.006, + "Bahorel": 0.002, + "Bossuet": 0.032, + "Joly": 0.002, + "Grantaire": 0.000, + "MotherPlutarch": 0.000, + "Gueulemer": 0.005, + "Babet": 0.005, + "Claquesous": 0.005, + "Montparnasse": 0.004, + "Toussaint": 0.000, + "Child1": 0.000, + "Child2": 0.000, + "Brujon": 0.000, + "MmeHucheloup": 0.000, + } + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_unnormalized_k5_load(self): + G = self.K5 + c = nx.load_centrality(G, normalized=False) + d = {0: 0.000, 1: 0.000, 2: 0.000, 3: 0.000, 4: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_unnormalized_p3_load(self): + G = self.P3 + c = nx.load_centrality(G, normalized=False) + d = {0: 0.000, 1: 2.000, 2: 0.000} + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_unnormalized_krackhardt_load(self): + G = self.K + c = nx.load_centrality(G, normalized=False) + d = { + 0: 1.667, + 1: 1.667, + 2: 0.000, + 3: 7.333, + 4: 0.000, + 5: 16.667, + 6: 16.667, + 7: 28.000, + 8: 16.000, + 9: 0.000, + } + + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_unnormalized_florentine_families_load(self): + G = self.F + c = nx.load_centrality(G, normalized=False) + + d = { + "Acciaiuoli": 0.000, + "Albizzi": 38.333, + "Barbadori": 17.000, + "Bischeri": 19.000, + "Castellani": 10.000, + "Ginori": 0.000, + "Guadagni": 45.667, + "Lamberteschi": 0.000, + "Medici": 95.000, + "Pazzi": 0.000, + "Peruzzi": 4.000, + "Ridolfi": 21.333, + "Salviati": 26.000, + "Strozzi": 19.333, + "Tornabuoni": 16.333, + } + for n in sorted(G): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_load_betweenness_difference(self): + # Difference Between Load and Betweenness + # --------------------------------------- The smallest graph + # that shows the difference between load and betweenness is + # G=ladder_graph(3) (Graph B below) + + # Graph A and B are from Tao Zhou, Jian-Guo Liu, Bing-Hong + # Wang: Comment on "Scientific collaboration + # networks. II. Shortest paths, weighted networks, and + # centrality". https://arxiv.org/pdf/physics/0511084 + + # Notice that unlike here, their calculation adds to 1 to the + # betweenness of every node i for every path from i to every + # other node. This is exactly what it should be, based on + # Eqn. (1) in their paper: the eqn is B(v) = \sum_{s\neq t, + # s\neq v}{\frac{\sigma_{st}(v)}{\sigma_{st}}}, therefore, + # they allow v to be the target node. + + # We follow Brandes 2001, who follows Freeman 1977 that make + # the sum for betweenness of v exclude paths where v is either + # the source or target node. To agree with their numbers, we + # must additionally, remove edge (4,8) from the graph, see AC + # example following (there is a mistake in the figure in their + # paper - personal communication). + + # A = nx.Graph() + # A.add_edges_from([(0,1), (1,2), (1,3), (2,4), + # (3,5), (4,6), (4,7), (4,8), + # (5,8), (6,9), (7,9), (8,9)]) + B = nx.Graph() # ladder_graph(3) + B.add_edges_from([(0, 1), (0, 2), (1, 3), (2, 3), (2, 4), (4, 5), (3, 5)]) + c = nx.load_centrality(B, normalized=False) + d = {0: 1.750, 1: 1.750, 2: 6.500, 3: 6.500, 4: 1.750, 5: 1.750} + for n in sorted(B): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_c4_edge_load(self): + G = self.C4 + c = nx.edge_load_centrality(G) + d = {(0, 1): 6.000, (0, 3): 6.000, (1, 2): 6.000, (2, 3): 6.000} + for n in G.edges(): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_p4_edge_load(self): + G = self.P4 + c = nx.edge_load_centrality(G) + d = {(0, 1): 6.000, (1, 2): 8.000, (2, 3): 6.000} + for n in G.edges(): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_k5_edge_load(self): + G = self.K5 + c = nx.edge_load_centrality(G) + d = { + (0, 1): 5.000, + (0, 2): 5.000, + (0, 3): 5.000, + (0, 4): 5.000, + (1, 2): 5.000, + (1, 3): 5.000, + (1, 4): 5.000, + (2, 3): 5.000, + (2, 4): 5.000, + (3, 4): 5.000, + } + for n in G.edges(): + assert c[n] == pytest.approx(d[n], abs=1e-3) + + def test_tree_edge_load(self): + G = self.T + c = nx.edge_load_centrality(G) + d = { + (0, 1): 24.000, + (0, 2): 24.000, + (1, 3): 12.000, + (1, 4): 12.000, + (2, 5): 12.000, + (2, 6): 12.000, + } + for n in G.edges(): + assert c[n] == pytest.approx(d[n], abs=1e-3) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_percolation_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_percolation_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..0cb8f52965c975013d41be7c3de874cd86ee693a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_percolation_centrality.py @@ -0,0 +1,87 @@ +import pytest + +import networkx as nx + + +def example1a_G(): + G = nx.Graph() + G.add_node(1, percolation=0.1) + G.add_node(2, percolation=0.2) + G.add_node(3, percolation=0.2) + G.add_node(4, percolation=0.2) + G.add_node(5, percolation=0.3) + G.add_node(6, percolation=0.2) + G.add_node(7, percolation=0.5) + G.add_node(8, percolation=0.5) + G.add_edges_from([(1, 4), (2, 4), (3, 4), (4, 5), (5, 6), (6, 7), (6, 8)]) + return G + + +def example1b_G(): + G = nx.Graph() + G.add_node(1, percolation=0.3) + G.add_node(2, percolation=0.5) + G.add_node(3, percolation=0.5) + G.add_node(4, percolation=0.2) + G.add_node(5, percolation=0.3) + G.add_node(6, percolation=0.2) + G.add_node(7, percolation=0.1) + G.add_node(8, percolation=0.1) + G.add_edges_from([(1, 4), (2, 4), (3, 4), (4, 5), (5, 6), (6, 7), (6, 8)]) + return G + + +def test_percolation_example1a(): + """percolation centrality: example 1a""" + G = example1a_G() + p = nx.percolation_centrality(G) + p_answer = {4: 0.625, 6: 0.667} + for n, k in p_answer.items(): + assert p[n] == pytest.approx(k, abs=1e-3) + + +def test_percolation_example1b(): + """percolation centrality: example 1a""" + G = example1b_G() + p = nx.percolation_centrality(G) + p_answer = {4: 0.825, 6: 0.4} + for n, k in p_answer.items(): + assert p[n] == pytest.approx(k, abs=1e-3) + + +def test_converge_to_betweenness(): + """percolation centrality: should converge to betweenness + centrality when all nodes are percolated the same""" + # taken from betweenness test test_florentine_families_graph + G = nx.florentine_families_graph() + b_answer = { + "Acciaiuoli": 0.000, + "Albizzi": 0.212, + "Barbadori": 0.093, + "Bischeri": 0.104, + "Castellani": 0.055, + "Ginori": 0.000, + "Guadagni": 0.255, + "Lamberteschi": 0.000, + "Medici": 0.522, + "Pazzi": 0.000, + "Peruzzi": 0.022, + "Ridolfi": 0.114, + "Salviati": 0.143, + "Strozzi": 0.103, + "Tornabuoni": 0.092, + } + + # If no initial state is provided, state for + # every node defaults to 1 + p_answer = nx.percolation_centrality(G) + assert p_answer == pytest.approx(b_answer, abs=1e-3) + + p_states = {k: 0.3 for k, v in b_answer.items()} + p_answer = nx.percolation_centrality(G, states=p_states) + assert p_answer == pytest.approx(b_answer, abs=1e-3) + + +def test_default_percolation(): + G = nx.erdos_renyi_graph(42, 0.42, seed=42) + assert nx.percolation_centrality(G) == pytest.approx(nx.betweenness_centrality(G)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_reaching.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_reaching.py new file mode 100644 index 0000000000000000000000000000000000000000..35d50e701216bc6c003517ca3ed92cddc261c286 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_reaching.py @@ -0,0 +1,140 @@ +"""Unit tests for the :mod:`networkx.algorithms.centrality.reaching` module.""" + +import pytest + +import networkx as nx + + +class TestGlobalReachingCentrality: + """Unit tests for the global reaching centrality function.""" + + def test_non_positive_weights(self): + with pytest.raises(nx.NetworkXError): + G = nx.DiGraph() + nx.global_reaching_centrality(G, weight="weight") + + def test_negatively_weighted(self): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, -2), (1, 2, +1)]) + nx.global_reaching_centrality(G, weight="weight") + + def test_directed_star(self): + G = nx.DiGraph() + G.add_weighted_edges_from([(1, 2, 0.5), (1, 3, 0.5)]) + grc = nx.global_reaching_centrality + assert grc(G, normalized=False, weight="weight") == 0.5 + assert grc(G) == 1 + + def test_undirected_unweighted_star(self): + G = nx.star_graph(2) + grc = nx.global_reaching_centrality + assert grc(G, normalized=False, weight=None) == 0.25 + + def test_undirected_weighted_star(self): + G = nx.Graph() + G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2)]) + grc = nx.global_reaching_centrality + assert grc(G, normalized=False, weight="weight") == 0.375 + + def test_cycle_directed_unweighted(self): + G = nx.DiGraph() + G.add_edge(1, 2) + G.add_edge(2, 1) + assert nx.global_reaching_centrality(G, weight=None) == 0 + + def test_cycle_undirected_unweighted(self): + G = nx.Graph() + G.add_edge(1, 2) + assert nx.global_reaching_centrality(G, weight=None) == 0 + + def test_cycle_directed_weighted(self): + G = nx.DiGraph() + G.add_weighted_edges_from([(1, 2, 1), (2, 1, 1)]) + assert nx.global_reaching_centrality(G) == 0 + + def test_cycle_undirected_weighted(self): + G = nx.Graph() + G.add_edge(1, 2, weight=1) + grc = nx.global_reaching_centrality + assert grc(G, normalized=False) == 0 + + def test_directed_weighted(self): + G = nx.DiGraph() + G.add_edge("A", "B", weight=5) + G.add_edge("B", "C", weight=1) + G.add_edge("B", "D", weight=0.25) + G.add_edge("D", "E", weight=1) + + denom = len(G) - 1 + A_local = sum([5, 3, 2.625, 2.0833333333333]) / denom + B_local = sum([1, 0.25, 0.625]) / denom + C_local = 0 + D_local = sum([1]) / denom + E_local = 0 + + local_reach_ctrs = [A_local, C_local, B_local, D_local, E_local] + max_local = max(local_reach_ctrs) + expected = sum(max_local - lrc for lrc in local_reach_ctrs) / denom + grc = nx.global_reaching_centrality + actual = grc(G, normalized=False, weight="weight") + assert expected == pytest.approx(actual, abs=1e-7) + + def test_single_node_with_cycle(self): + G = nx.DiGraph([(1, 1)]) + with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"): + nx.global_reaching_centrality(G) + + def test_single_node_with_weighted_cycle(self): + G = nx.DiGraph() + G.add_weighted_edges_from([(1, 1, 2)]) + with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"): + nx.global_reaching_centrality(G, weight="weight") + + +class TestLocalReachingCentrality: + """Unit tests for the local reaching centrality function.""" + + def test_non_positive_weights(self): + with pytest.raises(nx.NetworkXError): + G = nx.DiGraph() + G.add_weighted_edges_from([(0, 1, 0)]) + nx.local_reaching_centrality(G, 0, weight="weight") + + def test_negatively_weighted(self): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, -2), (1, 2, +1)]) + nx.local_reaching_centrality(G, 0, weight="weight") + + def test_undirected_unweighted_star(self): + G = nx.star_graph(2) + grc = nx.local_reaching_centrality + assert grc(G, 1, weight=None, normalized=False) == 0.75 + + def test_undirected_weighted_star(self): + G = nx.Graph() + G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2)]) + centrality = nx.local_reaching_centrality( + G, 1, normalized=False, weight="weight" + ) + assert centrality == 1.5 + + def test_undirected_weighted_normalized(self): + G = nx.Graph() + G.add_weighted_edges_from([(1, 2, 1), (1, 3, 2)]) + centrality = nx.local_reaching_centrality( + G, 1, normalized=True, weight="weight" + ) + assert centrality == 1.0 + + def test_single_node_with_cycle(self): + G = nx.DiGraph([(1, 1)]) + with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"): + nx.local_reaching_centrality(G, 1) + + def test_single_node_with_weighted_cycle(self): + G = nx.DiGraph() + G.add_weighted_edges_from([(1, 1, 2)]) + with pytest.raises(nx.NetworkXError, match="local_reaching_centrality"): + nx.local_reaching_centrality(G, 1, weight="weight") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_second_order_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_second_order_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..cc3047866079fd9fe4cf43a6793cf160a0c0cdce --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_second_order_centrality.py @@ -0,0 +1,82 @@ +""" +Tests for second order centrality. +""" + +import pytest + +pytest.importorskip("numpy") +pytest.importorskip("scipy") + +import networkx as nx + + +def test_empty(): + with pytest.raises(nx.NetworkXException): + G = nx.empty_graph() + nx.second_order_centrality(G) + + +def test_non_connected(): + with pytest.raises(nx.NetworkXException): + G = nx.Graph() + G.add_node(0) + G.add_node(1) + nx.second_order_centrality(G) + + +def test_non_negative_edge_weights(): + with pytest.raises(nx.NetworkXException): + G = nx.path_graph(2) + G.add_edge(0, 1, weight=-1) + nx.second_order_centrality(G) + + +def test_weight_attribute(): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, 1.0), (1, 2, 3.5)], weight="w") + expected = {0: 3.431, 1: 3.082, 2: 5.612} + b = nx.second_order_centrality(G, weight="w") + + for n in sorted(G): + assert b[n] == pytest.approx(expected[n], abs=1e-2) + + +def test_one_node_graph(): + """Second order centrality: single node""" + G = nx.Graph() + G.add_node(0) + G.add_edge(0, 0) + assert nx.second_order_centrality(G)[0] == 0 + + +def test_P3(): + """Second order centrality: line graph, as defined in paper""" + G = nx.path_graph(3) + b_answer = {0: 3.741, 1: 1.414, 2: 3.741} + + b = nx.second_order_centrality(G) + + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-2) + + +def test_K3(): + """Second order centrality: complete graph, as defined in paper""" + G = nx.complete_graph(3) + b_answer = {0: 1.414, 1: 1.414, 2: 1.414} + + b = nx.second_order_centrality(G) + + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-2) + + +def test_ring_graph(): + """Second order centrality: ring graph, as defined in paper""" + G = nx.cycle_graph(5) + b_answer = {0: 4.472, 1: 4.472, 2: 4.472, 3: 4.472, 4: 4.472} + + b = nx.second_order_centrality(G) + + for n in sorted(G): + assert b[n] == pytest.approx(b_answer[n], abs=1e-2) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_subgraph.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_subgraph.py new file mode 100644 index 0000000000000000000000000000000000000000..710927515baa4786e4be15ddf25ad34e423563d2 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_subgraph.py @@ -0,0 +1,110 @@ +import pytest + +pytest.importorskip("numpy") +pytest.importorskip("scipy") + +import networkx as nx +from networkx.algorithms.centrality.subgraph_alg import ( + communicability_betweenness_centrality, + estrada_index, + subgraph_centrality, + subgraph_centrality_exp, +) + + +class TestSubgraph: + def test_subgraph_centrality(self): + answer = {0: 1.5430806348152433, 1: 1.5430806348152433} + result = subgraph_centrality(nx.path_graph(2)) + for k, v in result.items(): + assert answer[k] == pytest.approx(v, abs=1e-7) + + answer1 = { + "1": 1.6445956054135658, + "Albert": 2.4368257358712189, + "Aric": 2.4368257358712193, + "Dan": 3.1306328496328168, + "Franck": 2.3876142275231915, + } + G1 = nx.Graph( + [ + ("Franck", "Aric"), + ("Aric", "Dan"), + ("Dan", "Albert"), + ("Albert", "Franck"), + ("Dan", "1"), + ("Franck", "Albert"), + ] + ) + result1 = subgraph_centrality(G1) + for k, v in result1.items(): + assert answer1[k] == pytest.approx(v, abs=1e-7) + result1 = subgraph_centrality_exp(G1) + for k, v in result1.items(): + assert answer1[k] == pytest.approx(v, abs=1e-7) + + def test_subgraph_centrality_big_graph(self): + g199 = nx.complete_graph(199) + g200 = nx.complete_graph(200) + + comm199 = nx.subgraph_centrality(g199) + comm199_exp = nx.subgraph_centrality_exp(g199) + + comm200 = nx.subgraph_centrality(g200) + comm200_exp = nx.subgraph_centrality_exp(g200) + + def test_communicability_betweenness_centrality_small(self): + result = communicability_betweenness_centrality(nx.path_graph(2)) + assert result == {0: 0, 1: 0} + + result = communicability_betweenness_centrality(nx.path_graph(1)) + assert result == {0: 0} + + result = communicability_betweenness_centrality(nx.path_graph(0)) + assert result == {} + + answer = {0: 0.1411224421177313, 1: 1.0, 2: 0.1411224421177313} + result = communicability_betweenness_centrality(nx.path_graph(3)) + for k, v in result.items(): + assert answer[k] == pytest.approx(v, abs=1e-7) + + result = communicability_betweenness_centrality(nx.complete_graph(3)) + for k, v in result.items(): + assert 0.49786143366223296 == pytest.approx(v, abs=1e-7) + + def test_communicability_betweenness_centrality(self): + answer = { + 0: 0.07017447951484615, + 1: 0.71565598701107991, + 2: 0.71565598701107991, + 3: 0.07017447951484615, + } + result = communicability_betweenness_centrality(nx.path_graph(4)) + for k, v in result.items(): + assert answer[k] == pytest.approx(v, abs=1e-7) + + answer1 = { + "1": 0.060039074193949521, + "Albert": 0.315470761661372, + "Aric": 0.31547076166137211, + "Dan": 0.68297778678316201, + "Franck": 0.21977926617449497, + } + G1 = nx.Graph( + [ + ("Franck", "Aric"), + ("Aric", "Dan"), + ("Dan", "Albert"), + ("Albert", "Franck"), + ("Dan", "1"), + ("Franck", "Albert"), + ] + ) + result1 = communicability_betweenness_centrality(G1) + for k, v in result1.items(): + assert answer1[k] == pytest.approx(v, abs=1e-7) + + def test_estrada_index(self): + answer = 1041.2470334195475 + result = estrada_index(nx.karate_club_graph()) + assert answer == pytest.approx(result, abs=1e-7) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_trophic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_trophic.py new file mode 100644 index 0000000000000000000000000000000000000000..8f7d74ddd86aeb127d8dcbc6250ec080affb0860 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_trophic.py @@ -0,0 +1,302 @@ +"""Test trophic levels, trophic differences and trophic coherence""" + +import pytest + +import networkx as nx + +np = pytest.importorskip("numpy") +pytest.importorskip("scipy") + + +def test_trophic_levels(): + """Trivial example""" + G = nx.DiGraph() + G.add_edge("a", "b") + G.add_edge("b", "c") + + d = nx.trophic_levels(G) + assert d == {"a": 1, "b": 2, "c": 3} + + +def test_trophic_levels_levine(): + """Example from Figure 5 in Stephen Levine (1980) J. theor. Biol. 83, + 195-207 + """ + S = nx.DiGraph() + S.add_edge(1, 2, weight=1.0) + S.add_edge(1, 3, weight=0.2) + S.add_edge(1, 4, weight=0.8) + S.add_edge(2, 3, weight=0.2) + S.add_edge(2, 5, weight=0.3) + S.add_edge(4, 3, weight=0.6) + S.add_edge(4, 5, weight=0.7) + S.add_edge(5, 4, weight=0.2) + + # save copy for later, test intermediate implementation details first + S2 = S.copy() + + # drop nodes of in-degree zero + z = [nid for nid, d in S.in_degree if d == 0] + for nid in z: + S.remove_node(nid) + + # find adjacency matrix + q = nx.linalg.graphmatrix.adjacency_matrix(S).T + + # fmt: off + expected_q = np.array([ + [0, 0, 0., 0], + [0.2, 0, 0.6, 0], + [0, 0, 0, 0.2], + [0.3, 0, 0.7, 0] + ]) + # fmt: on + assert np.array_equal(q.todense(), expected_q) + + # must be square, size of number of nodes + assert len(q.shape) == 2 + assert q.shape[0] == q.shape[1] + assert q.shape[0] == len(S) + + nn = q.shape[0] + + i = np.eye(nn) + n = np.linalg.inv(i - q) + y = np.asarray(n) @ np.ones(nn) + + expected_y = np.array([1, 2.07906977, 1.46511628, 2.3255814]) + assert np.allclose(y, expected_y) + + expected_d = {1: 1, 2: 2, 3: 3.07906977, 4: 2.46511628, 5: 3.3255814} + + d = nx.trophic_levels(S2) + + for nid, level in d.items(): + expected_level = expected_d[nid] + assert expected_level == pytest.approx(level, abs=1e-7) + + +def test_trophic_levels_simple(): + matrix_a = np.array([[0, 0], [1, 0]]) + G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph) + d = nx.trophic_levels(G) + assert d[0] == pytest.approx(2, abs=1e-7) + assert d[1] == pytest.approx(1, abs=1e-7) + + +def test_trophic_levels_more_complex(): + # fmt: off + matrix = np.array([ + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix, create_using=nx.DiGraph) + d = nx.trophic_levels(G) + expected_result = [1, 2, 3, 4] + for ind in range(4): + assert d[ind] == pytest.approx(expected_result[ind], abs=1e-7) + + # fmt: off + matrix = np.array([ + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix, create_using=nx.DiGraph) + d = nx.trophic_levels(G) + + expected_result = [1, 2, 2.5, 3.25] + for ind in range(4): + assert d[ind] == pytest.approx(expected_result[ind], abs=1e-7) + + +def test_trophic_levels_even_more_complex(): + # fmt: off + # Another, bigger matrix + matrix = np.array([ + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 0], + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 0, 1, 0] + ]) + # Generated this linear system using pen and paper: + K = np.array([ + [1, 0, -1, 0, 0], + [0, 0.5, 0, -0.5, 0], + [0, 0, 1, 0, 0], + [0, -0.5, 0, 1, -0.5], + [0, 0, 0, 0, 1], + ]) + # fmt: on + result_1 = np.ravel(np.linalg.inv(K) @ np.ones(5)) + G = nx.from_numpy_array(matrix, create_using=nx.DiGraph) + result_2 = nx.trophic_levels(G) + + for ind in range(5): + assert result_1[ind] == pytest.approx(result_2[ind], abs=1e-7) + + +def test_trophic_levels_singular_matrix(): + """Should raise an error with graphs with only non-basal nodes""" + matrix = np.identity(4) + G = nx.from_numpy_array(matrix, create_using=nx.DiGraph) + with pytest.raises(nx.NetworkXError, match="no basal nodes"): + nx.trophic_levels(G) + + +def test_trophic_levels_singular_with_basal(): + """Should fail to compute if there are any parts of the graph which are not + reachable from any basal node (with in-degree zero). + """ + G = nx.DiGraph() + # a has in-degree zero + G.add_edge("a", "b") + + # b is one level above a, c and d + G.add_edge("c", "b") + G.add_edge("d", "b") + + # c and d form a loop, neither are reachable from a + G.add_edge("c", "d") + G.add_edge("d", "c") + + with pytest.raises(nx.NetworkXError) as e: + nx.trophic_levels(G) + msg = ( + "Trophic levels are only defined for graphs where every node " + + "has a path from a basal node (basal nodes are nodes with no " + + "incoming edges)." + ) + assert msg in str(e.value) + + # if self-loops are allowed, smaller example: + G = nx.DiGraph() + G.add_edge("a", "b") # a has in-degree zero + G.add_edge("c", "b") # b is one level above a and c + G.add_edge("c", "c") # c has a self-loop + with pytest.raises(nx.NetworkXError) as e: + nx.trophic_levels(G) + msg = ( + "Trophic levels are only defined for graphs where every node " + + "has a path from a basal node (basal nodes are nodes with no " + + "incoming edges)." + ) + assert msg in str(e.value) + + +def test_trophic_differences(): + matrix_a = np.array([[0, 1], [0, 0]]) + G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph) + diffs = nx.trophic_differences(G) + assert diffs[(0, 1)] == pytest.approx(1, abs=1e-7) + + # fmt: off + matrix_b = np.array([ + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph) + diffs = nx.trophic_differences(G) + + assert diffs[(0, 1)] == pytest.approx(1, abs=1e-7) + assert diffs[(0, 2)] == pytest.approx(1.5, abs=1e-7) + assert diffs[(1, 2)] == pytest.approx(0.5, abs=1e-7) + assert diffs[(1, 3)] == pytest.approx(1.25, abs=1e-7) + assert diffs[(2, 3)] == pytest.approx(0.75, abs=1e-7) + + +def test_trophic_incoherence_parameter_no_cannibalism(): + matrix_a = np.array([[0, 1], [0, 0]]) + G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=False) + assert q == pytest.approx(0, abs=1e-7) + + # fmt: off + matrix_b = np.array([ + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=False) + assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7) + + # fmt: off + matrix_c = np.array([ + [0, 1, 1, 0], + [0, 1, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 1] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=False) + # Ignore the -link + assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7) + + # no self-loops case + # fmt: off + matrix_d = np.array([ + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_d, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=False) + # Ignore the -link + assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7) + + +def test_trophic_incoherence_parameter_cannibalism(): + matrix_a = np.array([[0, 1], [0, 0]]) + G = nx.from_numpy_array(matrix_a, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=True) + assert q == pytest.approx(0, abs=1e-7) + + # fmt: off + matrix_b = np.array([ + [0, 0, 0, 0, 0], + [0, 1, 0, 1, 0], + [1, 0, 0, 0, 0], + [0, 1, 0, 0, 0], + [0, 0, 0, 1, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_b, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=True) + assert q == pytest.approx(2, abs=1e-7) + + # fmt: off + matrix_c = np.array([ + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 1], + [0, 0, 0, 0] + ]) + # fmt: on + G = nx.from_numpy_array(matrix_c, create_using=nx.DiGraph) + q = nx.trophic_incoherence_parameter(G, cannibalism=True) + # Ignore the -link + assert q == pytest.approx(np.std([1, 1.5, 0.5, 0.75, 1.25]), abs=1e-7) + + +def test_no_basal_node(): + G = nx.DiGraph([(1, 2), (2, 3), (3, 1)]) # No basal node, should raise an error + with pytest.raises(nx.NetworkXError, match="no basal node"): + nx.trophic_levels(G) + G.add_node(4) # add basal node, but not connected + with pytest.raises(nx.NetworkXError, match="every node .* path from a basal node"): + nx.trophic_levels(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_voterank.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_voterank.py new file mode 100644 index 0000000000000000000000000000000000000000..a5cfb6107a64f1c1e0923297a81c7df7172d5afe --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/tests/test_voterank.py @@ -0,0 +1,64 @@ +""" +Unit tests for VoteRank. +""" + +import networkx as nx + + +class TestVoteRankCentrality: + # Example Graph present in reference paper + def test_voterank_centrality_1(self): + G = nx.Graph() + G.add_edges_from( + [ + (7, 8), + (7, 5), + (7, 9), + (5, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 6), + (2, 6), + (3, 6), + (4, 6), + ] + ) + assert [0, 7, 6] == nx.voterank(G) + + def test_voterank_emptygraph(self): + G = nx.Graph() + assert [] == nx.voterank(G) + + # Graph unit test + def test_voterank_centrality_2(self): + G = nx.florentine_families_graph() + d = nx.voterank(G, 4) + exact = ["Medici", "Strozzi", "Guadagni", "Castellani"] + assert exact == d + + # DiGraph unit test + def test_voterank_centrality_3(self): + G = nx.gnc_graph(10, seed=7) + d = nx.voterank(G, 4) + exact = [3, 6, 8] + assert exact == d + + # MultiGraph unit test + def test_voterank_centrality_4(self): + G = nx.MultiGraph() + G.add_edges_from( + [(0, 1), (0, 1), (1, 2), (2, 5), (2, 5), (5, 6), (5, 6), (2, 4), (4, 3)] + ) + exact = [2, 1, 5, 4] + assert exact == nx.voterank(G) + + # MultiDiGraph unit test + def test_voterank_centrality_5(self): + G = nx.MultiDiGraph() + G.add_edges_from( + [(0, 1), (0, 1), (1, 2), (2, 5), (2, 5), (5, 6), (5, 6), (2, 4), (4, 3)] + ) + exact = [2, 0, 5, 4] + assert exact == nx.voterank(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/trophic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/trophic.py new file mode 100644 index 0000000000000000000000000000000000000000..608c110603d12f79edf67a5e9c103a988b0a859e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/trophic.py @@ -0,0 +1,181 @@ +"""Trophic levels""" + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["trophic_levels", "trophic_differences", "trophic_incoherence_parameter"] + + +@not_implemented_for("undirected") +@nx._dispatchable(edge_attrs="weight") +def trophic_levels(G, weight="weight"): + r"""Compute the trophic levels of nodes. + + The trophic level of a node $i$ is + + .. math:: + + s_i = 1 + \frac{1}{k^{in}_i} \sum_{j} a_{ij} s_j + + where $k^{in}_i$ is the in-degree of i + + .. math:: + + k^{in}_i = \sum_{j} a_{ij} + + and nodes with $k^{in}_i = 0$ have $s_i = 1$ by convention. + + These are calculated using the method outlined in Levine [1]_. + + Parameters + ---------- + G : DiGraph + A directed networkx graph + + Returns + ------- + nodes : dict + Dictionary of nodes with trophic level as the value. + + References + ---------- + .. [1] Stephen Levine (1980) J. theor. Biol. 83, 195-207 + """ + + basal_nodes = [n for n, deg in G.in_degree if deg == 0] + if not basal_nodes: + raise nx.NetworkXError( + "This graph has no basal nodes (nodes with no incoming edges)." + "Trophic levels are not defined without at least one basal node." + ) + + reachable_nodes = { + node for layer in nx.bfs_layers(G, sources=basal_nodes) for node in layer + } + + if len(reachable_nodes) != len(G.nodes): + raise nx.NetworkXError( + "Trophic levels are only defined for graphs where every node has a path " + "from a basal node (basal nodes are nodes with no incoming edges)." + ) + + import numpy as np + + # find adjacency matrix + a = nx.adjacency_matrix(G, weight=weight).T.toarray() + + # drop rows/columns where in-degree is zero + rowsum = np.sum(a, axis=1) + p = a[rowsum != 0][:, rowsum != 0] + # normalise so sum of in-degree weights is 1 along each row + p = p / rowsum[rowsum != 0][:, np.newaxis] + + # calculate trophic levels + nn = p.shape[0] + i = np.eye(nn) + try: + n = np.linalg.inv(i - p) + except np.linalg.LinAlgError as err: + # LinAlgError is raised when there is a non-basal node + msg = ( + "Trophic levels are only defined for graphs where every " + + "node has a path from a basal node (basal nodes are nodes " + + "with no incoming edges)." + ) + raise nx.NetworkXError(msg) from err + y = n.sum(axis=1) + 1 + + levels = {} + + # all nodes with in-degree zero have trophic level == 1 + zero_node_ids = (node_id for node_id, degree in G.in_degree if degree == 0) + for node_id in zero_node_ids: + levels[node_id] = 1 + + # all other nodes have levels as calculated + nonzero_node_ids = (node_id for node_id, degree in G.in_degree if degree != 0) + for i, node_id in enumerate(nonzero_node_ids): + levels[node_id] = y.item(i) + + return levels + + +@not_implemented_for("undirected") +@nx._dispatchable(edge_attrs="weight") +def trophic_differences(G, weight="weight"): + r"""Compute the trophic differences of the edges of a directed graph. + + The trophic difference $x_ij$ for each edge is defined in Johnson et al. + [1]_ as: + + .. math:: + x_ij = s_j - s_i + + Where $s_i$ is the trophic level of node $i$. + + Parameters + ---------- + G : DiGraph + A directed networkx graph + + Returns + ------- + diffs : dict + Dictionary of edges with trophic differences as the value. + + References + ---------- + .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. + Munoz (2014) PNAS "Trophic coherence determines food-web stability" + """ + levels = trophic_levels(G, weight=weight) + diffs = {} + for u, v in G.edges: + diffs[(u, v)] = levels[v] - levels[u] + return diffs + + +@not_implemented_for("undirected") +@nx._dispatchable(edge_attrs="weight") +def trophic_incoherence_parameter(G, weight="weight", cannibalism=False): + r"""Compute the trophic incoherence parameter of a graph. + + Trophic coherence is defined as the homogeneity of the distribution of + trophic distances: the more similar, the more coherent. This is measured by + the standard deviation of the trophic differences and referred to as the + trophic incoherence parameter $q$ by [1]. + + Parameters + ---------- + G : DiGraph + A directed networkx graph + + cannibalism: Boolean + If set to False, self edges are not considered in the calculation + + Returns + ------- + trophic_incoherence_parameter : float + The trophic coherence of a graph + + References + ---------- + .. [1] Samuel Johnson, Virginia Dominguez-Garcia, Luca Donetti, Miguel A. + Munoz (2014) PNAS "Trophic coherence determines food-web stability" + """ + import numpy as np + + if cannibalism: + diffs = trophic_differences(G, weight=weight) + else: + # If no cannibalism, remove self-edges + self_loops = list(nx.selfloop_edges(G)) + if self_loops: + # Make a copy so we do not change G's edges in memory + G_2 = G.copy() + G_2.remove_edges_from(self_loops) + else: + # Avoid copy otherwise + G_2 = G + diffs = trophic_differences(G_2, weight=weight) + return float(np.std(list(diffs.values()))) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/voterank_alg.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/voterank_alg.py new file mode 100644 index 0000000000000000000000000000000000000000..9b510b2886a5e2eca1eb3396f55f67abc4ce9f30 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/centrality/voterank_alg.py @@ -0,0 +1,95 @@ +"""Algorithm to select influential nodes in a graph using VoteRank.""" + +import networkx as nx + +__all__ = ["voterank"] + + +@nx._dispatchable +def voterank(G, number_of_nodes=None): + """Select a list of influential nodes in a graph using VoteRank algorithm + + VoteRank [1]_ computes a ranking of the nodes in a graph G based on a + voting scheme. With VoteRank, all nodes vote for each of its in-neighbors + and the node with the highest votes is elected iteratively. The voting + ability of out-neighbors of elected nodes is decreased in subsequent turns. + + Parameters + ---------- + G : graph + A NetworkX graph. + + number_of_nodes : integer, optional + Number of ranked nodes to extract (default all nodes). + + Returns + ------- + voterank : list + Ordered list of computed seeds. + Only nodes with positive number of votes are returned. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (0, 3), (1, 4)]) + >>> nx.voterank(G) + [0, 1] + + The algorithm can be used both for undirected and directed graphs. + However, the directed version is different in two ways: + (i) nodes only vote for their in-neighbors and + (ii) only the voting ability of elected node and its out-neighbors are updated: + + >>> G = nx.DiGraph([(0, 1), (2, 1), (2, 3), (3, 4)]) + >>> nx.voterank(G) + [2, 3] + + Notes + ----- + Each edge is treated independently in case of multigraphs. + + References + ---------- + .. [1] Zhang, J.-X. et al. (2016). + Identifying a set of influential spreaders in complex networks. + Sci. Rep. 6, 27823; doi: 10.1038/srep27823. + """ + influential_nodes = [] + vote_rank = {} + if len(G) == 0: + return influential_nodes + if number_of_nodes is None or number_of_nodes > len(G): + number_of_nodes = len(G) + if G.is_directed(): + # For directed graphs compute average out-degree + avgDegree = sum(deg for _, deg in G.out_degree()) / len(G) + else: + # For undirected graphs compute average degree + avgDegree = sum(deg for _, deg in G.degree()) / len(G) + # step 1 - initiate all nodes to (0,1) (score, voting ability) + for n in G.nodes(): + vote_rank[n] = [0, 1] + # Repeat steps 1b to 4 until num_seeds are elected. + for _ in range(number_of_nodes): + # step 1b - reset rank + for n in G.nodes(): + vote_rank[n][0] = 0 + # step 2 - vote + for n, nbr in G.edges(): + # In directed graphs nodes only vote for their in-neighbors + vote_rank[n][0] += vote_rank[nbr][1] + if not G.is_directed(): + vote_rank[nbr][0] += vote_rank[n][1] + for n in influential_nodes: + vote_rank[n][0] = 0 + # step 3 - select top node + n = max(G.nodes, key=lambda x: vote_rank[x][0]) + if vote_rank[n][0] == 0: + return influential_nodes + influential_nodes.append(n) + # weaken the selected node + vote_rank[n] = [0, 0] + # step 4 - update voterank properties + for _, nbr in G.edges(n): + vote_rank[nbr][1] -= 1 / avgDegree + vote_rank[nbr][1] = max(vote_rank[nbr][1], 0) + return influential_nodes diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..39381d9f163a5400f362b91a89215bfc915a8022 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__init__.py @@ -0,0 +1,4 @@ +from networkx.algorithms.coloring.greedy_coloring import * +from networkx.algorithms.coloring.equitable_coloring import equitable_color + +__all__ = ["greedy_color", "equitable_color"] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..58a3f5097447abc1ad2f62a2854303cdb2826fd1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/equitable_coloring.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/equitable_coloring.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b23abf408caf37416624d4d1304a5b543a8d25ff Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/equitable_coloring.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/greedy_coloring.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/greedy_coloring.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..caeb43530b3dcfd1482fec76823ce760fb11ada6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/__pycache__/greedy_coloring.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/equitable_coloring.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/equitable_coloring.py new file mode 100644 index 0000000000000000000000000000000000000000..e464a07447045fcdaa8e7ca4ea56552fb00e2826 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/equitable_coloring.py @@ -0,0 +1,505 @@ +""" +Equitable coloring of graphs with bounded degree. +""" + +from collections import defaultdict + +import networkx as nx + +__all__ = ["equitable_color"] + + +@nx._dispatchable +def is_coloring(G, coloring): + """Determine if the coloring is a valid coloring for the graph G.""" + # Verify that the coloring is valid. + return all(coloring[s] != coloring[d] for s, d in G.edges) + + +@nx._dispatchable +def is_equitable(G, coloring, num_colors=None): + """Determines if the coloring is valid and equitable for the graph G.""" + + if not is_coloring(G, coloring): + return False + + # Verify whether it is equitable. + color_set_size = defaultdict(int) + for color in coloring.values(): + color_set_size[color] += 1 + + if num_colors is not None: + for color in range(num_colors): + if color not in color_set_size: + # These colors do not have any vertices attached to them. + color_set_size[color] = 0 + + # If there are more than 2 distinct values, the coloring cannot be equitable + all_set_sizes = set(color_set_size.values()) + if len(all_set_sizes) == 0 and num_colors is None: # Was an empty graph + return True + elif len(all_set_sizes) == 1: + return True + elif len(all_set_sizes) == 2: + a, b = list(all_set_sizes) + return abs(a - b) <= 1 + else: # len(all_set_sizes) > 2: + return False + + +def make_C_from_F(F): + C = defaultdict(list) + for node, color in F.items(): + C[color].append(node) + + return C + + +def make_N_from_L_C(L, C): + nodes = L.keys() + colors = C.keys() + return { + (node, color): sum(1 for v in L[node] if v in C[color]) + for node in nodes + for color in colors + } + + +def make_H_from_C_N(C, N): + return { + (c1, c2): sum(1 for node in C[c1] if N[(node, c2)] == 0) for c1 in C for c2 in C + } + + +def change_color(u, X, Y, N, H, F, C, L): + """Change the color of 'u' from X to Y and update N, H, F, C.""" + assert F[u] == X and X != Y + + # Change the class of 'u' from X to Y + F[u] = Y + + for k in C: + # 'u' witnesses an edge from k -> Y instead of from k -> X now. + if N[u, k] == 0: + H[(X, k)] -= 1 + H[(Y, k)] += 1 + + for v in L[u]: + # 'v' has lost a neighbor in X and gained one in Y + N[(v, X)] -= 1 + N[(v, Y)] += 1 + + if N[(v, X)] == 0: + # 'v' witnesses F[v] -> X + H[(F[v], X)] += 1 + + if N[(v, Y)] == 1: + # 'v' no longer witnesses F[v] -> Y + H[(F[v], Y)] -= 1 + + C[X].remove(u) + C[Y].append(u) + + +def move_witnesses(src_color, dst_color, N, H, F, C, T_cal, L): + """Move witness along a path from src_color to dst_color.""" + X = src_color + while X != dst_color: + Y = T_cal[X] + # Move _any_ witness from X to Y = T_cal[X] + w = next(x for x in C[X] if N[(x, Y)] == 0) + change_color(w, X, Y, N=N, H=H, F=F, C=C, L=L) + X = Y + + +@nx._dispatchable(mutates_input=True) +def pad_graph(G, num_colors): + """Add a disconnected complete clique K_p such that the number of nodes in + the graph becomes a multiple of `num_colors`. + + Assumes that the graph's nodes are labelled using integers. + + Returns the number of nodes with each color. + """ + + n_ = len(G) + r = num_colors - 1 + + # Ensure that the number of nodes in G is a multiple of (r + 1) + s = n_ // (r + 1) + if n_ != s * (r + 1): + p = (r + 1) - n_ % (r + 1) + s += 1 + + # Complete graph K_p between (imaginary) nodes [n_, ... , n_ + p] + K = nx.relabel_nodes(nx.complete_graph(p), {idx: idx + n_ for idx in range(p)}) + G.add_edges_from(K.edges) + + return s + + +def procedure_P(V_minus, V_plus, N, H, F, C, L, excluded_colors=None): + """Procedure P as described in the paper.""" + + if excluded_colors is None: + excluded_colors = set() + + A_cal = set() + T_cal = {} + R_cal = [] + + # BFS to determine A_cal, i.e. colors reachable from V- + reachable = [V_minus] + marked = set(reachable) + idx = 0 + + while idx < len(reachable): + pop = reachable[idx] + idx += 1 + + A_cal.add(pop) + R_cal.append(pop) + + # TODO: Checking whether a color has been visited can be made faster by + # using a look-up table instead of testing for membership in a set by a + # logarithmic factor. + next_layer = [] + for k in C: + if ( + H[(k, pop)] > 0 + and k not in A_cal + and k not in excluded_colors + and k not in marked + ): + next_layer.append(k) + + for dst in next_layer: + # Record that `dst` can reach `pop` + T_cal[dst] = pop + + marked.update(next_layer) + reachable.extend(next_layer) + + # Variables for the algorithm + b = len(C) - len(A_cal) + + if V_plus in A_cal: + # Easy case: V+ is in A_cal + # Move one node from V+ to V- using T_cal to find the parents. + move_witnesses(V_plus, V_minus, N=N, H=H, F=F, C=C, T_cal=T_cal, L=L) + else: + # If there is a solo edge, we can resolve the situation by + # moving witnesses from B to A, making G[A] equitable and then + # recursively balancing G[B - w] with a different V_minus and + # but the same V_plus. + + A_0 = set() + A_cal_0 = set() + num_terminal_sets_found = 0 + made_equitable = False + + for W_1 in R_cal[::-1]: + for v in C[W_1]: + X = None + + for U in C: + if N[(v, U)] == 0 and U in A_cal and U != W_1: + X = U + + # v does not witness an edge in H[A_cal] + if X is None: + continue + + for U in C: + # Note: Departing from the paper here. + if N[(v, U)] >= 1 and U not in A_cal: + X_prime = U + w = v + + try: + # Finding the solo neighbor of w in X_prime + y = next( + node + for node in L[w] + if F[node] == X_prime and N[(node, W_1)] == 1 + ) + except StopIteration: + pass + else: + W = W_1 + + # Move w from W to X, now X has one extra node. + change_color(w, W, X, N=N, H=H, F=F, C=C, L=L) + + # Move witness from X to V_minus, making the coloring + # equitable. + move_witnesses( + src_color=X, + dst_color=V_minus, + N=N, + H=H, + F=F, + C=C, + T_cal=T_cal, + L=L, + ) + + # Move y from X_prime to W, making W the correct size. + change_color(y, X_prime, W, N=N, H=H, F=F, C=C, L=L) + + # Then call the procedure on G[B - y] + procedure_P( + V_minus=X_prime, + V_plus=V_plus, + N=N, + H=H, + C=C, + F=F, + L=L, + excluded_colors=excluded_colors.union(A_cal), + ) + made_equitable = True + break + + if made_equitable: + break + else: + # No node in W_1 was found such that + # it had a solo-neighbor. + A_cal_0.add(W_1) + A_0.update(C[W_1]) + num_terminal_sets_found += 1 + + if num_terminal_sets_found == b: + # Otherwise, construct the maximal independent set and find + # a pair of z_1, z_2 as in Case II. + + # BFS to determine B_cal': the set of colors reachable from V+ + B_cal_prime = set() + T_cal_prime = {} + + reachable = [V_plus] + marked = set(reachable) + idx = 0 + while idx < len(reachable): + pop = reachable[idx] + idx += 1 + + B_cal_prime.add(pop) + + # No need to check for excluded_colors here because + # they only exclude colors from A_cal + next_layer = [ + k + for k in C + if H[(pop, k)] > 0 and k not in B_cal_prime and k not in marked + ] + + for dst in next_layer: + T_cal_prime[pop] = dst + + marked.update(next_layer) + reachable.extend(next_layer) + + # Construct the independent set of G[B'] + I_set = set() + I_covered = set() + W_covering = {} + + B_prime = [node for k in B_cal_prime for node in C[k]] + + # Add the nodes in V_plus to I first. + for z in C[V_plus] + B_prime: + if z in I_covered or F[z] not in B_cal_prime: + continue + + I_set.add(z) + I_covered.add(z) + I_covered.update(list(L[z])) + + for w in L[z]: + if F[w] in A_cal_0 and N[(z, F[w])] == 1: + if w not in W_covering: + W_covering[w] = z + else: + # Found z1, z2 which have the same solo + # neighbor in some W + z_1 = W_covering[w] + # z_2 = z + + Z = F[z_1] + W = F[w] + + # shift nodes along W, V- + move_witnesses( + W, V_minus, N=N, H=H, F=F, C=C, T_cal=T_cal, L=L + ) + + # shift nodes along V+ to Z + move_witnesses( + V_plus, + Z, + N=N, + H=H, + F=F, + C=C, + T_cal=T_cal_prime, + L=L, + ) + + # change color of z_1 to W + change_color(z_1, Z, W, N=N, H=H, F=F, C=C, L=L) + + # change color of w to some color in B_cal + W_plus = next( + k for k in C if N[(w, k)] == 0 and k not in A_cal + ) + change_color(w, W, W_plus, N=N, H=H, F=F, C=C, L=L) + + # recurse with G[B \cup W*] + excluded_colors.update( + [k for k in C if k != W and k not in B_cal_prime] + ) + procedure_P( + V_minus=W, + V_plus=W_plus, + N=N, + H=H, + C=C, + F=F, + L=L, + excluded_colors=excluded_colors, + ) + + made_equitable = True + break + + if made_equitable: + break + else: + assert False, ( + "Must find a w which is the solo neighbor " + "of two vertices in B_cal_prime." + ) + + if made_equitable: + break + + +@nx._dispatchable +def equitable_color(G, num_colors): + """Provides an equitable coloring for nodes of `G`. + + Attempts to color a graph using `num_colors` colors, where no neighbors of + a node can have same color as the node itself and the number of nodes with + each color differ by at most 1. `num_colors` must be greater than the + maximum degree of `G`. The algorithm is described in [1]_ and has + complexity O(num_colors * n**2). + + Parameters + ---------- + G : networkX graph + The nodes of this graph will be colored. + + num_colors : number of colors to use + This number must be at least one more than the maximum degree of nodes + in the graph. + + Returns + ------- + A dictionary with keys representing nodes and values representing + corresponding coloring. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> nx.coloring.equitable_color(G, num_colors=3) # doctest: +SKIP + {0: 2, 1: 1, 2: 2, 3: 0} + + Raises + ------ + NetworkXAlgorithmError + If `num_colors` is not at least the maximum degree of the graph `G` + + References + ---------- + .. [1] Kierstead, H. A., Kostochka, A. V., Mydlarz, M., & Szemerédi, E. + (2010). A fast algorithm for equitable coloring. Combinatorica, 30(2), + 217-224. + """ + + # Map nodes to integers for simplicity later. + nodes_to_int = {} + int_to_nodes = {} + + for idx, node in enumerate(G.nodes): + nodes_to_int[node] = idx + int_to_nodes[idx] = node + + G = nx.relabel_nodes(G, nodes_to_int, copy=True) + + # Basic graph statistics and sanity check. + if len(G.nodes) > 0: + r_ = max(G.degree(node) for node in G.nodes) + else: + r_ = 0 + + if r_ >= num_colors: + raise nx.NetworkXAlgorithmError( + f"Graph has maximum degree {r_}, needs " + f"{r_ + 1} (> {num_colors}) colors for guaranteed coloring." + ) + + # Ensure that the number of nodes in G is a multiple of (r + 1) + pad_graph(G, num_colors) + + # Starting the algorithm. + # L = {node: list(G.neighbors(node)) for node in G.nodes} + L_ = {node: [] for node in G.nodes} + + # Arbitrary equitable allocation of colors to nodes. + F = {node: idx % num_colors for idx, node in enumerate(G.nodes)} + + C = make_C_from_F(F) + + # The neighborhood is empty initially. + N = make_N_from_L_C(L_, C) + + # Currently all nodes witness all edges. + H = make_H_from_C_N(C, N) + + # Start of algorithm. + edges_seen = set() + + for u in sorted(G.nodes): + for v in sorted(G.neighbors(u)): + # Do not double count edges if (v, u) has already been seen. + if (v, u) in edges_seen: + continue + + edges_seen.add((u, v)) + + L_[u].append(v) + L_[v].append(u) + + N[(u, F[v])] += 1 + N[(v, F[u])] += 1 + + if F[u] != F[v]: + # Were 'u' and 'v' witnesses for F[u] -> F[v] or F[v] -> F[u]? + if N[(u, F[v])] == 1: + H[F[u], F[v]] -= 1 # u cannot witness an edge between F[u], F[v] + + if N[(v, F[u])] == 1: + H[F[v], F[u]] -= 1 # v cannot witness an edge between F[v], F[u] + + if N[(u, F[u])] != 0: + # Find the first color where 'u' does not have any neighbors. + Y = next(k for k in C if N[(u, k)] == 0) + X = F[u] + change_color(u, X, Y, N=N, H=H, F=F, C=C, L=L_) + + # Procedure P + procedure_P(V_minus=X, V_plus=Y, N=N, H=H, F=F, C=C, L=L_) + + return {int_to_nodes[x]: F[x] for x in int_to_nodes} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/greedy_coloring.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/greedy_coloring.py new file mode 100644 index 0000000000000000000000000000000000000000..311bc3a929f76dc2cac0b85bc02459ce0a29ffd0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/greedy_coloring.py @@ -0,0 +1,565 @@ +""" +Greedy graph coloring using various strategies. +""" + +import itertools +from collections import defaultdict, deque + +import networkx as nx +from networkx.utils import arbitrary_element, py_random_state + +__all__ = [ + "greedy_color", + "strategy_connected_sequential", + "strategy_connected_sequential_bfs", + "strategy_connected_sequential_dfs", + "strategy_independent_set", + "strategy_largest_first", + "strategy_random_sequential", + "strategy_saturation_largest_first", + "strategy_smallest_last", +] + + +def strategy_largest_first(G, colors): + """Returns a list of the nodes of ``G`` in decreasing order by + degree. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + """ + return sorted(G, key=G.degree, reverse=True) + + +@py_random_state(2) +def strategy_random_sequential(G, colors, seed=None): + """Returns a random permutation of the nodes of ``G`` as a list. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + """ + nodes = list(G) + seed.shuffle(nodes) + return nodes + + +def strategy_smallest_last(G, colors): + """Returns a deque of the nodes of ``G``, "smallest" last. + + Specifically, the degrees of each node are tracked in a bucket queue. + From this, the node of minimum degree is repeatedly popped from the + graph, updating its neighbors' degrees. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + This implementation of the strategy runs in $O(n + m)$ time + (ignoring polylogarithmic factors), where $n$ is the number of nodes + and $m$ is the number of edges. + + This strategy is related to :func:`strategy_independent_set`: if we + interpret each node removed as an independent set of size one, then + this strategy chooses an independent set of size one instead of a + maximal independent set. + + """ + H = G.copy() + result = deque() + + # Build initial degree list (i.e. the bucket queue data structure) + degrees = defaultdict(set) # set(), for fast random-access removals + lbound = float("inf") + for node, d in H.degree(): + degrees[d].add(node) + lbound = min(lbound, d) # Lower bound on min-degree. + + def find_min_degree(): + # Save time by starting the iterator at `lbound`, not 0. + # The value that we find will be our new `lbound`, which we set later. + return next(d for d in itertools.count(lbound) if d in degrees) + + for _ in G: + # Pop a min-degree node and add it to the list. + min_degree = find_min_degree() + u = degrees[min_degree].pop() + if not degrees[min_degree]: # Clean up the degree list. + del degrees[min_degree] + result.appendleft(u) + + # Update degrees of removed node's neighbors. + for v in H[u]: + degree = H.degree(v) + degrees[degree].remove(v) + if not degrees[degree]: # Clean up the degree list. + del degrees[degree] + degrees[degree - 1].add(v) + + # Finally, remove the node. + H.remove_node(u) + lbound = min_degree - 1 # Subtract 1 in case of tied neighbors. + + return result + + +def _maximal_independent_set(G): + """Returns a maximal independent set of nodes in ``G`` by repeatedly + choosing an independent node of minimum degree (with respect to the + subgraph of unchosen nodes). + + """ + result = set() + remaining = set(G) + while remaining: + G = G.subgraph(remaining) + v = min(remaining, key=G.degree) + result.add(v) + remaining -= set(G[v]) | {v} + return result + + +def strategy_independent_set(G, colors): + """Uses a greedy independent set removal strategy to determine the + colors. + + This function updates ``colors`` **in-place** and return ``None``, + unlike the other strategy functions in this module. + + This algorithm repeatedly finds and removes a maximal independent + set, assigning each node in the set an unused color. + + ``G`` is a NetworkX graph. + + This strategy is related to :func:`strategy_smallest_last`: in that + strategy, an independent set of size one is chosen at each step + instead of a maximal independent set. + + """ + remaining_nodes = set(G) + while len(remaining_nodes) > 0: + nodes = _maximal_independent_set(G.subgraph(remaining_nodes)) + remaining_nodes -= nodes + yield from nodes + + +def strategy_connected_sequential_bfs(G, colors): + """Returns an iterable over nodes in ``G`` in the order given by a + breadth-first traversal. + + The generated sequence has the property that for each node except + the first, at least one neighbor appeared earlier in the sequence. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + """ + return strategy_connected_sequential(G, colors, "bfs") + + +def strategy_connected_sequential_dfs(G, colors): + """Returns an iterable over nodes in ``G`` in the order given by a + depth-first traversal. + + The generated sequence has the property that for each node except + the first, at least one neighbor appeared earlier in the sequence. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + """ + return strategy_connected_sequential(G, colors, "dfs") + + +def strategy_connected_sequential(G, colors, traversal="bfs"): + """Returns an iterable over nodes in ``G`` in the order given by a + breadth-first or depth-first traversal. + + ``traversal`` must be one of the strings ``'dfs'`` or ``'bfs'``, + representing depth-first traversal or breadth-first traversal, + respectively. + + The generated sequence has the property that for each node except + the first, at least one neighbor appeared earlier in the sequence. + + ``G`` is a NetworkX graph. ``colors`` is ignored. + + """ + if traversal == "bfs": + traverse = nx.bfs_edges + elif traversal == "dfs": + traverse = nx.dfs_edges + else: + raise nx.NetworkXError( + "Please specify one of the strings 'bfs' or" + " 'dfs' for connected sequential ordering" + ) + for component in nx.connected_components(G): + source = arbitrary_element(component) + # Yield the source node, then all the nodes in the specified + # traversal order. + yield source + for _, end in traverse(G.subgraph(component), source): + yield end + + +def strategy_saturation_largest_first(G, colors): + """Iterates over all the nodes of ``G`` in "saturation order" (also + known as "DSATUR"). + + ``G`` is a NetworkX graph. ``colors`` is a dictionary mapping nodes of + ``G`` to colors, for those nodes that have already been colored. + + """ + distinct_colors = {v: set() for v in G} + + # Add the node color assignments given in colors to the + # distinct colors set for each neighbor of that node + for node, color in colors.items(): + for neighbor in G[node]: + distinct_colors[neighbor].add(color) + + # Check that the color assignments in colors are valid + # i.e. no neighboring nodes have the same color + if len(colors) >= 2: + for node, color in colors.items(): + if color in distinct_colors[node]: + raise nx.NetworkXError("Neighboring nodes must have different colors") + + # If 0 nodes have been colored, simply choose the node of highest degree. + if not colors: + node = max(G, key=G.degree) + yield node + # Add the color 0 to the distinct colors set for each + # neighbor of that node. + for v in G[node]: + distinct_colors[v].add(0) + + while len(G) != len(colors): + # Update the distinct color sets for the neighbors. + for node, color in colors.items(): + for neighbor in G[node]: + distinct_colors[neighbor].add(color) + + # Compute the maximum saturation and the set of nodes that + # achieve that saturation. + saturation = {v: len(c) for v, c in distinct_colors.items() if v not in colors} + # Yield the node with the highest saturation, and break ties by + # degree. + node = max(saturation, key=lambda v: (saturation[v], G.degree(v))) + yield node + + +#: Dictionary mapping name of a strategy as a string to the strategy function. +STRATEGIES = { + "largest_first": strategy_largest_first, + "random_sequential": strategy_random_sequential, + "smallest_last": strategy_smallest_last, + "independent_set": strategy_independent_set, + "connected_sequential_bfs": strategy_connected_sequential_bfs, + "connected_sequential_dfs": strategy_connected_sequential_dfs, + "connected_sequential": strategy_connected_sequential, + "saturation_largest_first": strategy_saturation_largest_first, + "DSATUR": strategy_saturation_largest_first, +} + + +@nx._dispatchable +def greedy_color(G, strategy="largest_first", interchange=False): + """Color a graph using various strategies of greedy graph coloring. + + Attempts to color a graph using as few colors as possible, where no + neighbors of a node can have same color as the node itself. The + given strategy determines the order in which nodes are colored. + + The strategies are described in [1]_, and smallest-last is based on + [2]_. + + Parameters + ---------- + G : NetworkX graph + + strategy : string or function(G, colors) + A function (or a string representing a function) that provides + the coloring strategy, by returning nodes in the ordering they + should be colored. ``G`` is the graph, and ``colors`` is a + dictionary of the currently assigned colors, keyed by nodes. The + function must return an iterable over all the nodes in ``G``. + + If the strategy function is an iterator generator (that is, a + function with ``yield`` statements), keep in mind that the + ``colors`` dictionary will be updated after each ``yield``, since + this function chooses colors greedily. + + If ``strategy`` is a string, it must be one of the following, + each of which represents one of the built-in strategy functions. + + * ``'largest_first'`` + * ``'random_sequential'`` + * ``'smallest_last'`` + * ``'independent_set'`` + * ``'connected_sequential_bfs'`` + * ``'connected_sequential_dfs'`` + * ``'connected_sequential'`` (alias for the previous strategy) + * ``'saturation_largest_first'`` + * ``'DSATUR'`` (alias for the previous strategy) + + interchange: bool + Will use the color interchange algorithm described by [3]_ if set + to ``True``. + + Note that ``saturation_largest_first`` and ``independent_set`` + do not work with interchange. Furthermore, if you use + interchange with your own strategy function, you cannot rely + on the values in the ``colors`` argument. + + Returns + ------- + A dictionary with keys representing nodes and values representing + corresponding coloring. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> d = nx.coloring.greedy_color(G, strategy="largest_first") + >>> d in [{0: 0, 1: 1, 2: 0, 3: 1}, {0: 1, 1: 0, 2: 1, 3: 0}] + True + + Raises + ------ + NetworkXPointlessConcept + If ``strategy`` is ``saturation_largest_first`` or + ``independent_set`` and ``interchange`` is ``True``. + + References + ---------- + .. [1] Adrian Kosowski, and Krzysztof Manuszewski, + Classical Coloring of Graphs, Graph Colorings, 2-19, 2004. + ISBN 0-8218-3458-4. + .. [2] David W. Matula, and Leland L. Beck, "Smallest-last + ordering and clustering and graph coloring algorithms." *J. ACM* 30, + 3 (July 1983), 417–427. + .. [3] Maciej M. Sysło, Narsingh Deo, Janusz S. Kowalik, + Discrete Optimization Algorithms with Pascal Programs, 415-424, 1983. + ISBN 0-486-45353-7. + + """ + if len(G) == 0: + return {} + # Determine the strategy provided by the caller. + strategy = STRATEGIES.get(strategy, strategy) + if not callable(strategy): + raise nx.NetworkXError( + f"strategy must be callable or a valid string. {strategy} not valid." + ) + # Perform some validation on the arguments before executing any + # strategy functions. + if interchange: + if strategy is strategy_independent_set: + msg = "interchange cannot be used with independent_set" + raise nx.NetworkXPointlessConcept(msg) + if strategy is strategy_saturation_largest_first: + msg = "interchange cannot be used with saturation_largest_first" + raise nx.NetworkXPointlessConcept(msg) + colors = {} + nodes = strategy(G, colors) + if interchange: + return _greedy_coloring_with_interchange(G, nodes) + for u in nodes: + # Set to keep track of colors of neighbors + nbr_colors = {colors[v] for v in G[u] if v in colors} + # Find the first unused color. + for color in itertools.count(): + if color not in nbr_colors: + break + # Assign the new color to the current node. + colors[u] = color + return colors + + +# Tools for coloring with interchanges +class _Node: + __slots__ = ["node_id", "color", "adj_list", "adj_color"] + + def __init__(self, node_id, n): + self.node_id = node_id + self.color = -1 + self.adj_list = None + self.adj_color = [None for _ in range(n)] + + def __repr__(self): + return ( + f"Node_id: {self.node_id}, Color: {self.color}, " + f"Adj_list: ({self.adj_list}), adj_color: ({self.adj_color})" + ) + + def assign_color(self, adj_entry, color): + adj_entry.col_prev = None + adj_entry.col_next = self.adj_color[color] + self.adj_color[color] = adj_entry + if adj_entry.col_next is not None: + adj_entry.col_next.col_prev = adj_entry + + def clear_color(self, adj_entry, color): + if adj_entry.col_prev is None: + self.adj_color[color] = adj_entry.col_next + else: + adj_entry.col_prev.col_next = adj_entry.col_next + if adj_entry.col_next is not None: + adj_entry.col_next.col_prev = adj_entry.col_prev + + def iter_neighbors(self): + adj_node = self.adj_list + while adj_node is not None: + yield adj_node + adj_node = adj_node.next + + def iter_neighbors_color(self, color): + adj_color_node = self.adj_color[color] + while adj_color_node is not None: + yield adj_color_node.node_id + adj_color_node = adj_color_node.col_next + + +class _AdjEntry: + __slots__ = ["node_id", "next", "mate", "col_next", "col_prev"] + + def __init__(self, node_id): + self.node_id = node_id + self.next = None + self.mate = None + self.col_next = None + self.col_prev = None + + def __repr__(self): + col_next = None if self.col_next is None else self.col_next.node_id + col_prev = None if self.col_prev is None else self.col_prev.node_id + return ( + f"Node_id: {self.node_id}, Next: ({self.next}), " + f"Mate: ({self.mate.node_id}), " + f"col_next: ({col_next}), col_prev: ({col_prev})" + ) + + +def _greedy_coloring_with_interchange(G, nodes): + """Return a coloring for `original_graph` using interchange approach + + This procedure is an adaption of the algorithm described by [1]_, + and is an implementation of coloring with interchange. Please be + advised, that the datastructures used are rather complex because + they are optimized to minimize the time spent identifying + subcomponents of the graph, which are possible candidates for color + interchange. + + Parameters + ---------- + G : NetworkX graph + The graph to be colored + + nodes : list + nodes ordered using the strategy of choice + + Returns + ------- + dict : + A dictionary keyed by node to a color value + + References + ---------- + .. [1] Maciej M. Syslo, Narsingh Deo, Janusz S. Kowalik, + Discrete Optimization Algorithms with Pascal Programs, 415-424, 1983. + ISBN 0-486-45353-7. + """ + n = len(G) + + graph = {node: _Node(node, n) for node in G} + + for node1, node2 in G.edges(): + adj_entry1 = _AdjEntry(node2) + adj_entry2 = _AdjEntry(node1) + adj_entry1.mate = adj_entry2 + adj_entry2.mate = adj_entry1 + node1_head = graph[node1].adj_list + adj_entry1.next = node1_head + graph[node1].adj_list = adj_entry1 + node2_head = graph[node2].adj_list + adj_entry2.next = node2_head + graph[node2].adj_list = adj_entry2 + + k = 0 + for node in nodes: + # Find the smallest possible, unused color + neighbors = graph[node].iter_neighbors() + col_used = {graph[adj_node.node_id].color for adj_node in neighbors} + col_used.discard(-1) + k1 = next(itertools.dropwhile(lambda x: x in col_used, itertools.count())) + + # k1 is now the lowest available color + if k1 > k: + connected = True + visited = set() + col1 = -1 + col2 = -1 + while connected and col1 < k: + col1 += 1 + neighbor_cols = graph[node].iter_neighbors_color(col1) + col1_adj = list(neighbor_cols) + + col2 = col1 + while connected and col2 < k: + col2 += 1 + visited = set(col1_adj) + frontier = list(col1_adj) + i = 0 + while i < len(frontier): + search_node = frontier[i] + i += 1 + col_opp = col2 if graph[search_node].color == col1 else col1 + neighbor_cols = graph[search_node].iter_neighbors_color(col_opp) + + for neighbor in neighbor_cols: + if neighbor not in visited: + visited.add(neighbor) + frontier.append(neighbor) + + # Search if node is not adj to any col2 vertex + connected = ( + len( + visited.intersection(graph[node].iter_neighbors_color(col2)) + ) + > 0 + ) + + # If connected is false then we can swap !!! + if not connected: + # Update all the nodes in the component + for search_node in visited: + graph[search_node].color = ( + col2 if graph[search_node].color == col1 else col1 + ) + col2_adj = graph[search_node].adj_color[col2] + graph[search_node].adj_color[col2] = graph[search_node].adj_color[ + col1 + ] + graph[search_node].adj_color[col1] = col2_adj + + # Update all the neighboring nodes + for search_node in visited: + col = graph[search_node].color + col_opp = col1 if col == col2 else col2 + for adj_node in graph[search_node].iter_neighbors(): + if graph[adj_node.node_id].color != col_opp: + # Direct reference to entry + adj_mate = adj_node.mate + graph[adj_node.node_id].clear_color(adj_mate, col_opp) + graph[adj_node.node_id].assign_color(adj_mate, col) + k1 = col1 + + # We can color this node color k1 + graph[node].color = k1 + k = max(k1, k) + + # Update the neighbors of this node + for adj_node in graph[node].iter_neighbors(): + adj_mate = adj_node.mate + graph[adj_node.node_id].assign_color(adj_mate, k1) + + return {node.node_id: node.color for node in graph.values()} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c71f626548b3d2c071401dddab58940e38c81a96 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/test_coloring.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/test_coloring.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e4b443935a33a020777ce00618a1b59ede881c3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/__pycache__/test_coloring.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/test_coloring.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/test_coloring.py new file mode 100644 index 0000000000000000000000000000000000000000..1e5a913c7c07bc0060274e611cef18b054d71238 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/coloring/tests/test_coloring.py @@ -0,0 +1,863 @@ +"""Greedy coloring test suite.""" + +import itertools + +import pytest + +import networkx as nx + +is_coloring = nx.algorithms.coloring.equitable_coloring.is_coloring +is_equitable = nx.algorithms.coloring.equitable_coloring.is_equitable + + +ALL_STRATEGIES = [ + "largest_first", + "random_sequential", + "smallest_last", + "independent_set", + "connected_sequential_bfs", + "connected_sequential_dfs", + "connected_sequential", + "saturation_largest_first", + "DSATUR", +] + +# List of strategies where interchange=True results in an error +INTERCHANGE_INVALID = ["independent_set", "saturation_largest_first", "DSATUR"] + + +class TestColoring: + def test_basic_cases(self): + def check_basic_case(graph_func, n_nodes, strategy, interchange): + graph = graph_func() + coloring = nx.coloring.greedy_color( + graph, strategy=strategy, interchange=interchange + ) + assert verify_length(coloring, n_nodes) + assert verify_coloring(graph, coloring) + + for graph_func, n_nodes in BASIC_TEST_CASES.items(): + for interchange in [True, False]: + for strategy in ALL_STRATEGIES: + check_basic_case(graph_func, n_nodes, strategy, False) + if strategy not in INTERCHANGE_INVALID: + check_basic_case(graph_func, n_nodes, strategy, True) + + def test_special_cases(self): + def check_special_case(strategy, graph_func, interchange, colors): + graph = graph_func() + coloring = nx.coloring.greedy_color( + graph, strategy=strategy, interchange=interchange + ) + if not hasattr(colors, "__len__"): + colors = [colors] + assert any(verify_length(coloring, n_colors) for n_colors in colors) + assert verify_coloring(graph, coloring) + + for strategy, arglist in SPECIAL_TEST_CASES.items(): + for args in arglist: + check_special_case(strategy, args[0], args[1], args[2]) + + def test_interchange_invalid(self): + graph = one_node_graph() + for strategy in INTERCHANGE_INVALID: + pytest.raises( + nx.NetworkXPointlessConcept, + nx.coloring.greedy_color, + graph, + strategy=strategy, + interchange=True, + ) + + def test_bad_inputs(self): + graph = one_node_graph() + pytest.raises( + nx.NetworkXError, + nx.coloring.greedy_color, + graph, + strategy="invalid strategy", + ) + + def test_strategy_as_function(self): + graph = lf_shc() + colors_1 = nx.coloring.greedy_color(graph, "largest_first") + colors_2 = nx.coloring.greedy_color(graph, nx.coloring.strategy_largest_first) + assert colors_1 == colors_2 + + def test_seed_argument(self): + graph = lf_shc() + rs = nx.coloring.strategy_random_sequential + c1 = nx.coloring.greedy_color(graph, lambda g, c: rs(g, c, seed=1)) + for u, v in graph.edges: + assert c1[u] != c1[v] + + def test_is_coloring(self): + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2)]) + coloring = {0: 0, 1: 1, 2: 0} + assert is_coloring(G, coloring) + + coloring[0] = 1 + assert not is_coloring(G, coloring) + assert not is_equitable(G, coloring) + + def test_is_equitable(self): + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2)]) + coloring = {0: 0, 1: 1, 2: 0} + assert is_equitable(G, coloring) + + G.add_edges_from([(2, 3), (2, 4), (2, 5)]) + coloring[3] = 1 + coloring[4] = 1 + coloring[5] = 1 + assert is_coloring(G, coloring) + assert not is_equitable(G, coloring) + + def test_num_colors(self): + G = nx.Graph() + G.add_edges_from([(0, 1), (0, 2), (0, 3)]) + pytest.raises(nx.NetworkXAlgorithmError, nx.coloring.equitable_color, G, 2) + + def test_equitable_color(self): + G = nx.fast_gnp_random_graph(n=10, p=0.2, seed=42) + coloring = nx.coloring.equitable_color(G, max_degree(G) + 1) + assert is_equitable(G, coloring) + + def test_equitable_color_empty(self): + G = nx.empty_graph() + coloring = nx.coloring.equitable_color(G, max_degree(G) + 1) + assert is_equitable(G, coloring) + + def test_equitable_color_large(self): + G = nx.fast_gnp_random_graph(100, 0.1, seed=42) + coloring = nx.coloring.equitable_color(G, max_degree(G) + 1) + assert is_equitable(G, coloring, num_colors=max_degree(G) + 1) + + def test_case_V_plus_not_in_A_cal(self): + # Hand crafted case to avoid the easy case. + L = { + 0: [2, 5], + 1: [3, 4], + 2: [0, 8], + 3: [1, 7], + 4: [1, 6], + 5: [0, 6], + 6: [4, 5], + 7: [3], + 8: [2], + } + + F = { + # Color 0 + 0: 0, + 1: 0, + # Color 1 + 2: 1, + 3: 1, + 4: 1, + 5: 1, + # Color 2 + 6: 2, + 7: 2, + 8: 2, + } + + C = nx.algorithms.coloring.equitable_coloring.make_C_from_F(F) + N = nx.algorithms.coloring.equitable_coloring.make_N_from_L_C(L, C) + H = nx.algorithms.coloring.equitable_coloring.make_H_from_C_N(C, N) + + nx.algorithms.coloring.equitable_coloring.procedure_P( + V_minus=0, V_plus=1, N=N, H=H, F=F, C=C, L=L + ) + check_state(L=L, N=N, H=H, F=F, C=C) + + def test_cast_no_solo(self): + L = { + 0: [8, 9], + 1: [10, 11], + 2: [8], + 3: [9], + 4: [10, 11], + 5: [8], + 6: [9], + 7: [10, 11], + 8: [0, 2, 5], + 9: [0, 3, 6], + 10: [1, 4, 7], + 11: [1, 4, 7], + } + + F = {0: 0, 1: 0, 2: 2, 3: 2, 4: 2, 5: 3, 6: 3, 7: 3, 8: 1, 9: 1, 10: 1, 11: 1} + + C = nx.algorithms.coloring.equitable_coloring.make_C_from_F(F) + N = nx.algorithms.coloring.equitable_coloring.make_N_from_L_C(L, C) + H = nx.algorithms.coloring.equitable_coloring.make_H_from_C_N(C, N) + + nx.algorithms.coloring.equitable_coloring.procedure_P( + V_minus=0, V_plus=1, N=N, H=H, F=F, C=C, L=L + ) + check_state(L=L, N=N, H=H, F=F, C=C) + + def test_hard_prob(self): + # Tests for two levels of recursion. + num_colors, s = 5, 5 + + G = nx.Graph() + G.add_edges_from( + [ + (0, 10), + (0, 11), + (0, 12), + (0, 23), + (10, 4), + (10, 9), + (10, 20), + (11, 4), + (11, 8), + (11, 16), + (12, 9), + (12, 22), + (12, 23), + (23, 7), + (1, 17), + (1, 18), + (1, 19), + (1, 24), + (17, 5), + (17, 13), + (17, 22), + (18, 5), + (19, 5), + (19, 6), + (19, 8), + (24, 7), + (24, 16), + (2, 4), + (2, 13), + (2, 14), + (2, 15), + (4, 6), + (13, 5), + (13, 21), + (14, 6), + (14, 15), + (15, 6), + (15, 21), + (3, 16), + (3, 20), + (3, 21), + (3, 22), + (16, 8), + (20, 8), + (21, 9), + (22, 7), + ] + ) + F = {node: node // s for node in range(num_colors * s)} + F[s - 1] = num_colors - 1 + + params = make_params_from_graph(G=G, F=F) + + nx.algorithms.coloring.equitable_coloring.procedure_P( + V_minus=0, V_plus=num_colors - 1, **params + ) + check_state(**params) + + def test_hardest_prob(self): + # Tests for two levels of recursion. + num_colors, s = 10, 4 + + G = nx.Graph() + G.add_edges_from( + [ + (0, 19), + (0, 24), + (0, 29), + (0, 30), + (0, 35), + (19, 3), + (19, 7), + (19, 9), + (19, 15), + (19, 21), + (19, 24), + (19, 30), + (19, 38), + (24, 5), + (24, 11), + (24, 13), + (24, 20), + (24, 30), + (24, 37), + (24, 38), + (29, 6), + (29, 10), + (29, 13), + (29, 15), + (29, 16), + (29, 17), + (29, 20), + (29, 26), + (30, 6), + (30, 10), + (30, 15), + (30, 22), + (30, 23), + (30, 39), + (35, 6), + (35, 9), + (35, 14), + (35, 18), + (35, 22), + (35, 23), + (35, 25), + (35, 27), + (1, 20), + (1, 26), + (1, 31), + (1, 34), + (1, 38), + (20, 4), + (20, 8), + (20, 14), + (20, 18), + (20, 28), + (20, 33), + (26, 7), + (26, 10), + (26, 14), + (26, 18), + (26, 21), + (26, 32), + (26, 39), + (31, 5), + (31, 8), + (31, 13), + (31, 16), + (31, 17), + (31, 21), + (31, 25), + (31, 27), + (34, 7), + (34, 8), + (34, 13), + (34, 18), + (34, 22), + (34, 23), + (34, 25), + (34, 27), + (38, 4), + (38, 9), + (38, 12), + (38, 14), + (38, 21), + (38, 27), + (2, 3), + (2, 18), + (2, 21), + (2, 28), + (2, 32), + (2, 33), + (2, 36), + (2, 37), + (2, 39), + (3, 5), + (3, 9), + (3, 13), + (3, 22), + (3, 23), + (3, 25), + (3, 27), + (18, 6), + (18, 11), + (18, 15), + (18, 39), + (21, 4), + (21, 10), + (21, 14), + (21, 36), + (28, 6), + (28, 10), + (28, 14), + (28, 16), + (28, 17), + (28, 25), + (28, 27), + (32, 5), + (32, 10), + (32, 12), + (32, 16), + (32, 17), + (32, 22), + (32, 23), + (33, 7), + (33, 10), + (33, 12), + (33, 16), + (33, 17), + (33, 25), + (33, 27), + (36, 5), + (36, 8), + (36, 15), + (36, 16), + (36, 17), + (36, 25), + (36, 27), + (37, 5), + (37, 11), + (37, 15), + (37, 16), + (37, 17), + (37, 22), + (37, 23), + (39, 7), + (39, 8), + (39, 15), + (39, 22), + (39, 23), + ] + ) + F = {node: node // s for node in range(num_colors * s)} + F[s - 1] = num_colors - 1 # V- = 0, V+ = num_colors - 1 + + params = make_params_from_graph(G=G, F=F) + + nx.algorithms.coloring.equitable_coloring.procedure_P( + V_minus=0, V_plus=num_colors - 1, **params + ) + check_state(**params) + + def test_strategy_saturation_largest_first(self): + def color_remaining_nodes( + G, + colored_nodes, + full_color_assignment=None, + nodes_to_add_between_calls=1, + ): + color_assignments = [] + aux_colored_nodes = colored_nodes.copy() + + node_iterator = nx.algorithms.coloring.greedy_coloring.strategy_saturation_largest_first( + G, aux_colored_nodes + ) + + for u in node_iterator: + # Set to keep track of colors of neighbors + nbr_colors = { + aux_colored_nodes[v] for v in G[u] if v in aux_colored_nodes + } + # Find the first unused color. + for color in itertools.count(): + if color not in nbr_colors: + break + aux_colored_nodes[u] = color + color_assignments.append((u, color)) + + # Color nodes between iterations + for i in range(nodes_to_add_between_calls - 1): + if not len(color_assignments) + len(colored_nodes) >= len( + full_color_assignment + ): + full_color_assignment_node, color = full_color_assignment[ + len(color_assignments) + len(colored_nodes) + ] + + # Assign the new color to the current node. + aux_colored_nodes[full_color_assignment_node] = color + color_assignments.append((full_color_assignment_node, color)) + + return color_assignments, aux_colored_nodes + + for G, _, _ in SPECIAL_TEST_CASES["saturation_largest_first"]: + G = G() + + # Check that function still works when nodes are colored between iterations + for nodes_to_add_between_calls in range(1, 5): + # Get a full color assignment, (including the order in which nodes were colored) + colored_nodes = {} + full_color_assignment, full_colored_nodes = color_remaining_nodes( + G, colored_nodes + ) + + # For each node in the color assignment, add it to colored_nodes and re-run the function + for ind, (node, color) in enumerate(full_color_assignment): + colored_nodes[node] = color + + ( + partial_color_assignment, + partial_colored_nodes, + ) = color_remaining_nodes( + G, + colored_nodes, + full_color_assignment=full_color_assignment, + nodes_to_add_between_calls=nodes_to_add_between_calls, + ) + + # Check that the color assignment and order of remaining nodes are the same + assert full_color_assignment[ind + 1 :] == partial_color_assignment + assert full_colored_nodes == partial_colored_nodes + + +# ############################ Utility functions ############################ +def verify_coloring(graph, coloring): + for node in graph.nodes(): + if node not in coloring: + return False + + color = coloring[node] + for neighbor in graph.neighbors(node): + if coloring[neighbor] == color: + return False + + return True + + +def verify_length(coloring, expected): + coloring = dict_to_sets(coloring) + return len(coloring) == expected + + +def dict_to_sets(colors): + if len(colors) == 0: + return [] + + k = max(colors.values()) + 1 + sets = [set() for _ in range(k)] + + for node, color in colors.items(): + sets[color].add(node) + + return sets + + +# ############################ Graph Generation ############################ + + +def empty_graph(): + return nx.Graph() + + +def one_node_graph(): + graph = nx.Graph() + graph.add_nodes_from([1]) + return graph + + +def two_node_graph(): + graph = nx.Graph() + graph.add_nodes_from([1, 2]) + graph.add_edges_from([(1, 2)]) + return graph + + +def three_node_clique(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3]) + graph.add_edges_from([(1, 2), (1, 3), (2, 3)]) + return graph + + +def disconnected(): + graph = nx.Graph() + graph.add_edges_from([(1, 2), (2, 3), (4, 5), (5, 6)]) + return graph + + +def rs_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4]) + graph.add_edges_from([(1, 2), (2, 3), (3, 4)]) + return graph + + +def slf_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7]) + graph.add_edges_from( + [(1, 2), (1, 5), (1, 6), (2, 3), (2, 7), (3, 4), (3, 7), (4, 5), (4, 6), (5, 6)] + ) + return graph + + +def slf_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) + graph.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 4), + (2, 6), + (5, 7), + (5, 8), + (6, 7), + (6, 8), + (7, 8), + ] + ) + return graph + + +def lf_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6]) + graph.add_edges_from([(6, 1), (1, 4), (4, 3), (3, 2), (2, 5)]) + return graph + + +def lf_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7]) + graph.add_edges_from( + [ + (1, 7), + (1, 6), + (1, 3), + (1, 4), + (7, 2), + (2, 6), + (2, 3), + (2, 5), + (5, 3), + (5, 4), + (4, 3), + ] + ) + return graph + + +def sl_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6]) + graph.add_edges_from( + [(1, 2), (1, 3), (2, 3), (1, 4), (2, 5), (3, 6), (4, 5), (4, 6), (5, 6)] + ) + return graph + + +def sl_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8]) + graph.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 5), + (1, 7), + (2, 3), + (2, 4), + (2, 8), + (8, 4), + (8, 6), + (8, 7), + (7, 5), + (7, 6), + (3, 4), + (4, 6), + (6, 5), + (5, 3), + ] + ) + return graph + + +def gis_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4]) + graph.add_edges_from([(1, 2), (2, 3), (3, 4)]) + return graph + + +def gis_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6]) + graph.add_edges_from([(1, 5), (2, 5), (3, 6), (4, 6), (5, 6)]) + return graph + + +def cs_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5]) + graph.add_edges_from([(1, 2), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (4, 5)]) + return graph + + +def rsi_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6]) + graph.add_edges_from( + [(1, 2), (1, 5), (1, 6), (2, 3), (3, 4), (4, 5), (4, 6), (5, 6)] + ) + return graph + + +def lfi_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7]) + graph.add_edges_from( + [(1, 2), (1, 5), (1, 6), (2, 3), (2, 7), (3, 4), (3, 7), (4, 5), (4, 6), (5, 6)] + ) + return graph + + +def lfi_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9]) + graph.add_edges_from( + [ + (1, 2), + (1, 5), + (1, 6), + (1, 7), + (2, 3), + (2, 8), + (2, 9), + (3, 4), + (3, 8), + (3, 9), + (4, 5), + (4, 6), + (4, 7), + (5, 6), + ] + ) + return graph + + +def sli_shc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7]) + graph.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 5), + (1, 7), + (2, 3), + (2, 6), + (3, 4), + (4, 5), + (4, 6), + (5, 7), + (6, 7), + ] + ) + return graph + + +def sli_hc(): + graph = nx.Graph() + graph.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9]) + graph.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 7), + (2, 8), + (2, 9), + (3, 6), + (3, 7), + (3, 9), + (4, 5), + (4, 6), + (4, 8), + (4, 9), + (5, 6), + (5, 7), + (5, 8), + (6, 7), + (6, 9), + (7, 8), + (8, 9), + ] + ) + return graph + + +# -------------------------------------------------------------------------- +# Basic tests for all strategies +# For each basic graph function, specify the number of expected colors. +BASIC_TEST_CASES = { + empty_graph: 0, + one_node_graph: 1, + two_node_graph: 2, + disconnected: 2, + three_node_clique: 3, +} + + +# -------------------------------------------------------------------------- +# Special test cases. Each strategy has a list of tuples of the form +# (graph function, interchange, valid # of colors) +SPECIAL_TEST_CASES = { + "random_sequential": [ + (rs_shc, False, (2, 3)), + (rs_shc, True, 2), + (rsi_shc, True, (3, 4)), + ], + "saturation_largest_first": [(slf_shc, False, (3, 4)), (slf_hc, False, 4)], + "largest_first": [ + (lf_shc, False, (2, 3)), + (lf_hc, False, 4), + (lf_shc, True, 2), + (lf_hc, True, 3), + (lfi_shc, True, (3, 4)), + (lfi_hc, True, 4), + ], + "smallest_last": [ + (sl_shc, False, (3, 4)), + (sl_hc, False, 5), + (sl_shc, True, 3), + (sl_hc, True, 4), + (sli_shc, True, (3, 4)), + (sli_hc, True, 5), + ], + "independent_set": [(gis_shc, False, (2, 3)), (gis_hc, False, 3)], + "connected_sequential": [(cs_shc, False, (3, 4)), (cs_shc, True, 3)], + "connected_sequential_dfs": [(cs_shc, False, (3, 4))], +} + + +# -------------------------------------------------------------------------- +# Helper functions to test +# (graph function, interchange, valid # of colors) + + +def check_state(L, N, H, F, C): + s = len(C[0]) + num_colors = len(C.keys()) + + assert all(u in L[v] for u in L for v in L[u]) + assert all(F[u] != F[v] for u in L for v in L[u]) + assert all(len(L[u]) < num_colors for u in L) + assert all(len(C[x]) == s for x in C) + assert all(H[(c1, c2)] >= 0 for c1 in C for c2 in C) + assert all(N[(u, F[u])] == 0 for u in F) + + +def max_degree(G): + """Get the maximum degree of any node in G.""" + return max(G.degree(node) for node in G.nodes) if len(G.nodes) > 0 else 0 + + +def make_params_from_graph(G, F): + """Returns {N, L, H, C} from the given graph.""" + num_nodes = len(G) + L = {u: [] for u in range(num_nodes)} + for u, v in G.edges: + L[u].append(v) + L[v].append(u) + + C = nx.algorithms.coloring.equitable_coloring.make_C_from_F(F) + N = nx.algorithms.coloring.equitable_coloring.make_N_from_L_C(L, C) + H = nx.algorithms.coloring.equitable_coloring.make_H_from_C_N(C, N) + + return {"N": N, "F": F, "C": C, "H": H, "L": L} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8014032c41e195bee307e4c638c627ca2ddf16c6 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__init__.py @@ -0,0 +1,28 @@ +"""Functions for computing and measuring community structure. + +The ``community`` subpackage can be accessed by using :mod:`networkx.community`, then accessing the +functions as attributes of ``community``. For example:: + + >>> import networkx as nx + >>> G = nx.barbell_graph(5, 1) + >>> communities_generator = nx.community.girvan_newman(G) + >>> top_level_communities = next(communities_generator) + >>> next_level_communities = next(communities_generator) + >>> sorted(map(sorted, next_level_communities)) + [[0, 1, 2, 3, 4], [5], [6, 7, 8, 9, 10]] + +""" + +from networkx.algorithms.community.asyn_fluid import * +from networkx.algorithms.community.bipartitions import * +from networkx.algorithms.community.centrality import * +from networkx.algorithms.community.divisive import * +from networkx.algorithms.community.kclique import * +from networkx.algorithms.community.label_propagation import * +from networkx.algorithms.community.lukes import * +from networkx.algorithms.community.modularity_max import * +from networkx.algorithms.community.quality import * +from networkx.algorithms.community.community_utils import * +from networkx.algorithms.community.louvain import * +from networkx.algorithms.community.leiden import * +from networkx.algorithms.community.local import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..548b8d4d08a2892f5480df2c2ffefbec5e3d81ac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/asyn_fluid.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/asyn_fluid.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31c944e5226037f1965ef03dcd573dd8a634843a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/asyn_fluid.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/bipartitions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/bipartitions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6bce2e3052a099e0b1c6f0a0b3267437a62b19a3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/bipartitions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..643b316a56cb7a1b99ba74f7b585ebc943601ae1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/community_utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/community_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b4f11d84f0c734bfe640192a4caf49937093d34 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/community_utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/divisive.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/divisive.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02d2eab2f380332e7da84d41204e005c2bcbc139 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/divisive.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/kclique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/kclique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08b5bcd04cbc9f68fb4aae455fcccc4726ac024c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/kclique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/label_propagation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/label_propagation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aca9fa0ae9ecb4f13517173bca2949be277d1829 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/label_propagation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/leiden.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/leiden.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb47eae216695407c2b82a22970d3282b55d8cad Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/leiden.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/local.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/local.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c5eddecafadd2af3c27f0611f51cd9dfee2fe74 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/local.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/louvain.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/louvain.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e05b74ed8aaff1b9fa8c5a43bcf24f331caae7f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/louvain.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/lukes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/lukes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71cd84e2d0195d3364823f60e0d6b692b9d7247a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/lukes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/modularity_max.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/modularity_max.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9131ecadf0e624d2d4b0580545cf1b0a1ef11f9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/modularity_max.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/quality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/quality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8af8c08722fa19cbc031c9693eed5f0404cbebd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/__pycache__/quality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/asyn_fluid.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/asyn_fluid.py new file mode 100644 index 0000000000000000000000000000000000000000..47a4dfcaa36ac5136d582ae28d92ee9210d90286 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/asyn_fluid.py @@ -0,0 +1,153 @@ +"""Asynchronous Fluid Communities algorithm for community detection.""" + +from collections import Counter + +import networkx as nx +from networkx.algorithms.components import is_connected +from networkx.exception import NetworkXError +from networkx.utils import groups, not_implemented_for, py_random_state + +__all__ = ["asyn_fluidc"] + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@py_random_state(3) +@nx._dispatchable +def asyn_fluidc(G, k, max_iter=100, seed=None): + """Returns communities in `G` as detected by Fluid Communities algorithm. + + The asynchronous fluid communities algorithm is described in + [1]_. The algorithm is based on the simple idea of fluids interacting + in an environment, expanding and pushing each other. Its initialization is + random, so found communities may vary on different executions. + + The algorithm proceeds as follows. First each of the initial k communities + is initialized in a random vertex in the graph. Then the algorithm iterates + over all vertices in a random order, updating the community of each vertex + based on its own community and the communities of its neighbors. This + process is performed several times until convergence. + At all times, each community has a total density of 1, which is equally + distributed among the vertices it contains. If a vertex changes of + community, vertex densities of affected communities are adjusted + immediately. When a complete iteration over all vertices is done, such that + no vertex changes the community it belongs to, the algorithm has converged + and returns. + + This is the original version of the algorithm described in [1]_. + Unfortunately, it does not support weighted graphs yet. + + Parameters + ---------- + G : NetworkX graph + Graph must be simple and undirected. + + k : integer + The number of communities to be found. + + max_iter : integer + The number of maximum iterations allowed. By default 100. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + communities : iterable + Iterable of communities given as sets of nodes. + + Notes + ----- + k variable is not an optional argument. + + References + ---------- + .. [1] Parés F., Garcia-Gasulla D. et al. "Fluid Communities: A + Competitive and Highly Scalable Community Detection Algorithm". + [https://arxiv.org/pdf/1703.09307.pdf]. + """ + # Initial checks + if not isinstance(k, int): + raise NetworkXError("k must be an integer.") + if not k > 0: + raise NetworkXError("k must be greater than 0.") + if not is_connected(G): + raise NetworkXError("Fluid Communities require connected Graphs.") + if len(G) < k: + raise NetworkXError("k cannot be bigger than the number of nodes.") + if max_iter <= 0: + msg = f"{max_iter=} must be greater than 0" + raise ValueError(msg) + # Initialization + max_density = 1.0 + vertices = list(G) + seed.shuffle(vertices) + communities = {n: i for i, n in enumerate(vertices[:k])} + density = {} + com_to_numvertices = {} + for vertex in communities: + com_to_numvertices[communities[vertex]] = 1 + density[communities[vertex]] = max_density + # Set up control variables and start iterating + iter_count = 0 + cont = True + while cont and iter_count < max_iter: + cont = False + iter_count += 1 + # Loop over all vertices in graph in a random order + vertices = list(G) + seed.shuffle(vertices) + for vertex in vertices: + # Updating rule + com_counter = Counter() + # Take into account self vertex community + try: + com_counter.update({communities[vertex]: density[communities[vertex]]}) + except KeyError: + pass + # Gather neighbor vertex communities + for v in G[vertex]: + try: + com_counter.update({communities[v]: density[communities[v]]}) + except KeyError: + continue + # Check which is the community with highest density + new_com = -1 + if len(com_counter.keys()) > 0: + max_freq = max(com_counter.values()) + best_communities = [ + com + for com, freq in com_counter.items() + if (max_freq - freq) < 0.0001 + ] + # If actual vertex com in best communities, it is preserved + try: + if communities[vertex] in best_communities: + new_com = communities[vertex] + except KeyError: + pass + + # If vertex community changes... + if new_com == -1: + # Set flag of non-convergence + cont = True + # Randomly chose a new community from candidates + new_com = seed.choice(best_communities) + # Update previous community status + try: + com_to_numvertices[communities[vertex]] -= 1 + density[communities[vertex]] = ( + max_density / com_to_numvertices[communities[vertex]] + ) + except KeyError: + pass + # Update new community status + communities[vertex] = new_com + com_to_numvertices[communities[vertex]] += 1 + density[communities[vertex]] = ( + max_density / com_to_numvertices[communities[vertex]] + ) + # If maximum iterations reached --> output actual results + # Return results by grouping communities as list of vertices + return iter(groups(communities).values()) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/bipartitions.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/bipartitions.py new file mode 100644 index 0000000000000000000000000000000000000000..5e0b44d6138850a85b179e9e19608a757230b9d3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/bipartitions.py @@ -0,0 +1,354 @@ +"""Functions for splitting a network into two communities (finding a bipartition).""" + +import random +from copy import deepcopy +from itertools import count + +import networkx as nx + +__all__ = [ + "kernighan_lin_bisection", + "spectral_modularity_bipartition", + "greedy_node_swap_bipartition", +] + + +def _kernighan_lin_sweep(edge_info, side): + """ + This is a modified form of Kernighan-Lin, which moves single nodes at a + time, alternating between sides to keep the bisection balanced. We keep + two min-heaps of swap costs to make optimal-next-move selection fast. + """ + heap0, heap1 = cost_heaps = nx.utils.BinaryHeap(), nx.utils.BinaryHeap() + # we use heap methods insert, pop, and get + for u, nbrs in edge_info.items(): + cost_u = sum(wt if side[v] else -wt for v, wt in nbrs.items()) + if side[u]: + heap1.insert(u, cost_u) + else: + heap0.insert(u, -cost_u) + + def _update_heap_values(node): + side_node = side[node] + for nbr, wt in edge_info[node].items(): + side_nbr = side[nbr] + if side_nbr == side_node: + wt = -wt + heap_nbr = cost_heaps[side_nbr] + if nbr in heap_nbr: + cost_nbr = heap_nbr.get(nbr) + 2 * wt + # allow_increase lets us update a value already on the heap + heap_nbr.insert(nbr, cost_nbr, allow_increase=True) + + i = 0 + totcost = 0 + while heap0 and heap1: + u, cost_u = heap0.pop() + _update_heap_values(u) + v, cost_v = heap1.pop() + _update_heap_values(v) + totcost += cost_u + cost_v + i += 1 + yield totcost, i, (u, v) + + +@nx.utils.not_implemented_for("directed") +@nx.utils.py_random_state(4) +@nx._dispatchable(edge_attrs="weight") +def kernighan_lin_bisection(G, partition=None, max_iter=10, weight="weight", seed=None): + """Partition a graph into two blocks using the Kernighan–Lin algorithm. + + This algorithm partitions a network into two sets by iteratively + swapping pairs of nodes to reduce the edge cut between the two sets. The + pairs are chosen according to a modified form of Kernighan-Lin [1]_, which + moves nodes individually, alternating between sides to keep the bisection + balanced. + + Kernighan-Lin is an approximate algorithm for maximal modularity bisection. + In [2]_ they suggest that fine-tuned improvements can be made using + greedy node swapping, (see `greedy_node_swap_bipartition`). + The improvements are typically only a few percent of the modularity value. + But they claim that can make a difference between a good and excellent method. + This function does not perform any improvements. But you can do that yourself. + + Parameters + ---------- + G : NetworkX graph + Graph must be undirected. + + partition : tuple + Pair of iterables containing an initial partition. If not + specified, a random balanced partition is used. + + max_iter : int + Maximum number of times to attempt swaps to find an + improvement before giving up. + + weight : string or function (default: "weight") + If this is a string, then edge weights will be accessed via the + edge attribute with this key (that is, the weight of the edge + joining `u` to `v` will be ``G.edges[u, v][weight]``). If no + such edge attribute exists, the weight of the edge is assumed to + be one. + + If this is a function, the weight of an edge is the value + returned by the function. The function must accept exactly three + positional arguments: the two endpoints of an edge and the + dictionary of edge attributes for that edge. The function must + return a number or None to indicate a hidden edge. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + Only used if partition is None + + Returns + ------- + partition : tuple + A pair of sets of nodes representing the bipartition. + + Raises + ------ + NetworkXError + If `partition` is not a valid partition of the nodes of the graph. + + References + ---------- + .. [1] Kernighan, B. W.; Lin, Shen (1970). + "An efficient heuristic procedure for partitioning graphs." + *Bell Systems Technical Journal* 49: 291--307. + Oxford University Press 2011. + .. [2] M. E. J. Newman, + "Modularity and community structure in networks", + PNAS, 103 (23), p. 8577-8582, + https://doi.org/10.1073/pnas.0601602103 + + """ + nodes = list(G) + + if partition is None: + seed.shuffle(nodes) + mid = len(nodes) // 2 + A, B = nodes[:mid], nodes[mid:] + else: + try: + A, B = partition + except (TypeError, ValueError) as err: + raise nx.NetworkXError("partition must be two sets") from err + if not nx.community.is_partition(G, [A, B]): + raise nx.NetworkXError("partition invalid") + + side = {node: (node in A) for node in nodes} + + # ruff: noqa: E731 skips check for no lambda functions + # Using shortest_paths _weight_function with sum instead of min on multiedges + if callable(weight): + sum_weight = weight + elif G.is_multigraph(): + sum_weight = lambda u, v, d: sum(dd.get(weight, 1) for dd in d.values()) + else: + sum_weight = lambda u, v, d: d.get(weight, 1) + + edge_info = { + u: {v: wt for v, d in nbrs.items() if (wt := sum_weight(u, v, d)) is not None} + for u, nbrs in G._adj.items() + } + + for i in range(max_iter): + costs = list(_kernighan_lin_sweep(edge_info, side)) + # find out how many edges to update: min_i + min_cost, min_i, _ = min(costs) + if min_cost >= 0: + break + + for _, _, (u, v) in costs[:min_i]: + side[u] = 1 + side[v] = 0 + + part1 = {u for u, s in side.items() if s == 0} + part2 = {u for u, s in side.items() if s == 1} + return part1, part2 + + +@nx.utils.not_implemented_for("directed") +@nx.utils.not_implemented_for("multigraph") +def spectral_modularity_bipartition(G): + r"""Return a bipartition of the nodes based on the spectrum of the + modularity matrix of the graph. + + This method calculates the eigenvector associated with the second + largest eigenvalue of the modularity matrix, where the modularity + matrix *B* is defined by + + ..math:: + + B_{i j} = A_{i j} - \frac{k_i k_j}{2 m}, + + where *A* is the adjacency matrix, `k_i` is the degree of node *i*, + and *m* is the number of edges in the graph. Nodes whose + corresponding values in the eigenvector are negative are placed in + one block, nodes whose values are nonnegative are placed in another + block. + + Parameters + ---------- + G : NetworkX graph + + Returns + ------- + C : tuple + Pair of communities as two sets of nodes of ``G``, partitioned + according to second largest eigenvalue of the modularity matrix. + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> MrHi, Officer = nx.community.spectral_modularity_bipartition(G) + >>> MrHi, Officer = sorted([sorted(MrHi), sorted(Officer)]) + >>> MrHi + [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21] + >>> Officer + [8, 9, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33] + + References + ---------- + .. [1] M. E. J. Newman *Networks: An Introduction*, pages 373--378 + Oxford University Press 2011. + .. [2] M. E. J. Newman, + "Modularity and community structure in networks", + PNAS, 103 (23), p. 8577-8582, + https://doi.org/10.1073/pnas.0601602103 + + """ + import numpy as np + + B = nx.linalg.modularity_matrix(G) + eigenvalues, eigenvectors = np.linalg.eig(B) + index = np.argsort(eigenvalues)[-1] + v2 = zip(np.real(eigenvectors[:, index]), G) + left, right = set(), set() + for u, n in v2: + if u < 0: + left.add(n) + else: + right.add(n) + return left, right + + +@nx.utils.not_implemented_for("multigraph") +def greedy_node_swap_bipartition(G, *, init_split=None, max_iter=10): + """Split the nodes into two communities based on greedy + modularity maximization. + + The algorithm works by selecting a node to change communities which + will maximize the modularity. The swap is made and the community + structure with the highest modularity is kept. + + Parameters + ---------- + G : NetworkX graph + + init_split : 2-tuple of sets of nodes + Pair of sets of nodes in ``G`` providing an initial bipartition + for the algorithm. If not specified, a random balanced partition + is used. If this pair of sets is not a partition of the nodes of `G`, + :exc:`NetworkXException` is raised. + + max_iter : int + Maximum number of iterations of attempting swaps to find an improvement. + + Returns + ------- + max_split : 2-tuple of sets of nodes + Pair of sets of nodes of ``G``, partitioned according to a + node swap greedy modularity maximization algorithm. + + Raises + ------ + NetworkXError + if init_split is not a valid partition of the + graph into two communities or if G is a MultiGraph + + Examples + -------- + >>> G = nx.barbell_graph(3, 0) + >>> left, right = nx.community.greedy_node_swap_bipartition(G) + >>> # Sort the communities so the nodes appear in increasing order. + >>> left, right = sorted([sorted(left), sorted(right)]) + >>> sorted(left) + [0, 1, 2] + >>> sorted(right) + [3, 4, 5] + + Notes + ----- + This function is not implemented for multigraphs. + + References + ---------- + .. [1] M. E. J. Newman "Networks: An Introduction", pages 373--375. + Oxford University Press 2011. + + """ + if init_split is None: + m1 = len(G) // 2 + m2 = len(G) - m1 + some_nodes = set(random.sample(list(G), m1)) + other_nodes = {n for n in G if n not in some_nodes} + best_split_so_far = (some_nodes, other_nodes) + else: + if not nx.community.is_partition(G, init_split): + raise nx.NetworkXError("init_split is not a partition of G") + if not len(init_split) == 2: + raise nx.NetworkXError("init_split must be a bipartition") + best_split_so_far = deepcopy(init_split) + + best_mod = nx.community.modularity(G, best_split_so_far) + + max_split, max_mod = best_split_so_far, best_mod + its = 0 + m = G.number_of_edges() + G_degree = dict(G.degree) + + while max_mod >= best_mod and its < max_iter: + best_split_so_far = max_split + best_mod = max_mod + next_split = deepcopy(max_split) + next_mod = max_mod + nodes = set(G) + while nodes: + max_swap = -1 + max_node = None + max_node_comm = None + left, right = next_split + leftd = sum(G_degree[n] for n in left) + rightd = sum(G_degree[n] for n in right) + for n in nodes: + if n in left: + in_comm, out_comm = left, right + in_deg, out_deg = leftd, rightd + else: + in_comm, out_comm = right, left + in_deg, out_deg = rightd, leftd + + d_eii = -len(G[n].keys() & in_comm) / m + d_ejj = len(G[n].keys() & out_comm) / m + deg = G_degree[n] + d_sum_ai = (deg / (2 * m**2)) * (in_deg - out_deg - deg) + swap_change = d_eii + d_ejj + d_sum_ai + + if swap_change > max_swap: + max_swap = swap_change + max_node = n + max_node_comm = in_comm + non_max_node_comm = out_comm + # swap the node from one comm to the other + max_node_comm.remove(max_node) + non_max_node_comm.add(max_node) + next_mod += max_swap + # deepcopy next_split each time it reaches a high (might go lower later) + if next_mod > max_mod: + max_split, max_mod = deepcopy(next_split), next_mod + nodes.remove(max_node) + its += 1 + return best_split_so_far diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..43281701d2b630710acba8f3cef6693356aa461a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/centrality.py @@ -0,0 +1,171 @@ +"""Functions for computing communities based on centrality notions.""" + +import networkx as nx + +__all__ = ["girvan_newman"] + + +@nx._dispatchable(preserve_edge_attrs="most_valuable_edge") +def girvan_newman(G, most_valuable_edge=None): + """Finds communities in a graph using the Girvan–Newman method. + + Parameters + ---------- + G : NetworkX graph + + most_valuable_edge : function + Function that takes a graph as input and outputs an edge. The + edge returned by this function will be recomputed and removed at + each iteration of the algorithm. + + If not specified, the edge with the highest + :func:`networkx.edge_betweenness_centrality` will be used. + + Returns + ------- + iterator + Iterator over tuples of sets of nodes in `G`. Each set of node + is a community, each tuple is a sequence of communities at a + particular level of the algorithm. + + Examples + -------- + To get the first pair of communities:: + + >>> G = nx.path_graph(10) + >>> comp = nx.community.girvan_newman(G) + >>> tuple(sorted(c) for c in next(comp)) + ([0, 1, 2, 3, 4], [5, 6, 7, 8, 9]) + + To get only the first *k* tuples of communities, use + :func:`itertools.islice`:: + + >>> import itertools + >>> G = nx.path_graph(8) + >>> k = 2 + >>> comp = nx.community.girvan_newman(G) + >>> for communities in itertools.islice(comp, k): + ... print(tuple(sorted(c) for c in communities)) + ... + ([0, 1, 2, 3], [4, 5, 6, 7]) + ([0, 1], [2, 3], [4, 5, 6, 7]) + + To stop getting tuples of communities once the number of communities + is greater than *k*, use :func:`itertools.takewhile`:: + + >>> import itertools + >>> G = nx.path_graph(8) + >>> k = 4 + >>> comp = nx.community.girvan_newman(G) + >>> limited = itertools.takewhile(lambda c: len(c) <= k, comp) + >>> for communities in limited: + ... print(tuple(sorted(c) for c in communities)) + ... + ([0, 1, 2, 3], [4, 5, 6, 7]) + ([0, 1], [2, 3], [4, 5, 6, 7]) + ([0, 1], [2, 3], [4, 5], [6, 7]) + + To just choose an edge to remove based on the weight:: + + >>> from operator import itemgetter + >>> G = nx.path_graph(10) + >>> edges = G.edges() + >>> nx.set_edge_attributes(G, {(u, v): v for u, v in edges}, "weight") + >>> def heaviest(G): + ... u, v, w = max(G.edges(data="weight"), key=itemgetter(2)) + ... return (u, v) + ... + >>> comp = nx.community.girvan_newman(G, most_valuable_edge=heaviest) + >>> tuple(sorted(c) for c in next(comp)) + ([0, 1, 2, 3, 4, 5, 6, 7, 8], [9]) + + To utilize edge weights when choosing an edge with, for example, the + highest betweenness centrality:: + + >>> from networkx import edge_betweenness_centrality as betweenness + >>> def most_central_edge(G): + ... centrality = betweenness(G, weight="weight") + ... return max(centrality, key=centrality.get) + ... + >>> G = nx.path_graph(10) + >>> comp = nx.community.girvan_newman(G, most_valuable_edge=most_central_edge) + >>> tuple(sorted(c) for c in next(comp)) + ([0, 1, 2, 3, 4], [5, 6, 7, 8, 9]) + + To specify a different ranking algorithm for edges, use the + `most_valuable_edge` keyword argument:: + + >>> from networkx import edge_betweenness_centrality + >>> from random import random + >>> def most_central_edge(G): + ... centrality = edge_betweenness_centrality(G) + ... max_cent = max(centrality.values()) + ... # Scale the centrality values so they are between 0 and 1, + ... # and add some random noise. + ... centrality = {e: c / max_cent for e, c in centrality.items()} + ... # Add some random noise. + ... centrality = {e: c + random() for e, c in centrality.items()} + ... return max(centrality, key=centrality.get) + ... + >>> G = nx.path_graph(10) + >>> comp = nx.community.girvan_newman(G, most_valuable_edge=most_central_edge) + + Notes + ----- + The Girvan–Newman algorithm detects communities by progressively + removing edges from the original graph. The algorithm removes the + "most valuable" edge, traditionally the edge with the highest + betweenness centrality, at each step. As the graph breaks down into + pieces, the tightly knit community structure is exposed and the + result can be depicted as a dendrogram. + + """ + # If the graph is already empty, simply return its connected + # components. + if G.number_of_edges() == 0: + yield tuple(nx.connected_components(G)) + return + # If no function is provided for computing the most valuable edge, + # use the edge betweenness centrality. + if most_valuable_edge is None: + + def most_valuable_edge(G): + """Returns the edge with the highest betweenness centrality + in the graph `G`. + + """ + # We have guaranteed that the graph is non-empty, so this + # dictionary will never be empty. + betweenness = nx.edge_betweenness_centrality(G) + return max(betweenness, key=betweenness.get) + + # The copy of G here must include the edge weight data. + g = G.copy().to_undirected() + # Self-loops must be removed because their removal has no effect on + # the connected components of the graph. + g.remove_edges_from(nx.selfloop_edges(g)) + while g.number_of_edges() > 0: + yield _without_most_central_edges(g, most_valuable_edge) + + +def _without_most_central_edges(G, most_valuable_edge): + """Returns the connected components of the graph that results from + repeatedly removing the most "valuable" edge in the graph. + + `G` must be a non-empty graph. This function modifies the graph `G` + in-place; that is, it removes edges on the graph `G`. + + `most_valuable_edge` is a function that takes the graph `G` as input + (or a subgraph with one or more edges of `G` removed) and returns an + edge. That edge will be removed and this process will be repeated + until the number of connected components in the graph increases. + + """ + original_num_components = nx.number_connected_components(G) + num_new_components = original_num_components + while num_new_components <= original_num_components: + edge = most_valuable_edge(G) + G.remove_edge(*edge) + new_components = tuple(nx.connected_components(G)) + num_new_components = len(new_components) + return new_components diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/community_utils.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/community_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ba73a6b30b28410b49babd8f996927a43931124d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/community_utils.py @@ -0,0 +1,30 @@ +"""Helper functions for community-finding algorithms.""" + +import networkx as nx + +__all__ = ["is_partition"] + + +@nx._dispatchable +def is_partition(G, communities): + """Returns *True* if `communities` is a partition of the nodes of `G`. + + A partition of a universe set is a family of pairwise disjoint sets + whose union is the entire universe set. + + Parameters + ---------- + G : NetworkX graph. + + communities : list or iterable of sets of nodes + If not a list, the iterable is converted internally to a list. + If it is an iterator it is exhausted. + + """ + # Alternate implementation: + # return all(sum(1 if v in c else 0 for c in communities) == 1 for v in G) + if not isinstance(communities, list): + communities = list(communities) + nodes = {n for c in communities for n in c if n in G} + + return len(G) == len(nodes) == sum(len(c) for c in communities) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/divisive.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/divisive.py new file mode 100644 index 0000000000000000000000000000000000000000..be3c7d863e9d28f6e9c56faea4a60a640f8892bb --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/divisive.py @@ -0,0 +1,216 @@ +import functools + +import networkx as nx + +__all__ = [ + "edge_betweenness_partition", + "edge_current_flow_betweenness_partition", +] + + +@nx._dispatchable(edge_attrs="weight") +def edge_betweenness_partition(G, number_of_sets, *, weight=None): + """Partition created by iteratively removing the highest edge betweenness edge. + + This algorithm works by calculating the edge betweenness for all + edges and removing the edge with the highest value. It is then + determined whether the graph has been broken into at least + `number_of_sets` connected components. + If not the process is repeated. + + Parameters + ---------- + G : NetworkX Graph, DiGraph or MultiGraph + Graph to be partitioned + + number_of_sets : int + Number of sets in the desired partition of the graph + + weight : key, optional, default=None + The key to use if using weights for edge betweenness calculation + + Returns + ------- + C : list of sets + Partition of the nodes of G + + Raises + ------ + NetworkXError + If number_of_sets is <= 0 or if number_of_sets > len(G) + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> part = nx.community.edge_betweenness_partition(G, 2) + >>> {0, 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21} in part + True + >>> { + ... 2, + ... 8, + ... 9, + ... 14, + ... 15, + ... 18, + ... 20, + ... 22, + ... 23, + ... 24, + ... 25, + ... 26, + ... 27, + ... 28, + ... 29, + ... 30, + ... 31, + ... 32, + ... 33, + ... } in part + True + + See Also + -------- + edge_current_flow_betweenness_partition + + Notes + ----- + This algorithm is fairly slow, as both the calculation of connected + components and edge betweenness relies on all pairs shortest + path algorithms. They could potentially be combined to cut down + on overall computation time. + + References + ---------- + .. [1] Santo Fortunato 'Community Detection in Graphs' Physical Reports + Volume 486, Issue 3-5 p. 75-174 + http://arxiv.org/abs/0906.0612 + """ + if number_of_sets <= 0: + raise nx.NetworkXError("number_of_sets must be >0") + if number_of_sets == 1: + return [set(G)] + if number_of_sets == len(G): + return [{n} for n in G] + if number_of_sets > len(G): + raise nx.NetworkXError("number_of_sets must be <= len(G)") + + H = G.copy() + partition = list(nx.connected_components(H)) + while len(partition) < number_of_sets: + ranking = nx.edge_betweenness_centrality(H, weight=weight) + edge = max(ranking, key=ranking.get) + H.remove_edge(*edge) + partition = list(nx.connected_components(H)) + return partition + + +@nx._dispatchable(edge_attrs="weight") +def edge_current_flow_betweenness_partition(G, number_of_sets, *, weight=None): + """Partition created by removing the highest edge current flow betweenness edge. + + This algorithm works by calculating the edge current flow + betweenness for all edges and removing the edge with the + highest value. It is then determined whether the graph has + been broken into at least `number_of_sets` connected + components. If not the process is repeated. + + Parameters + ---------- + G : NetworkX Graph, DiGraph or MultiGraph + Graph to be partitioned + + number_of_sets : int + Number of sets in the desired partition of the graph + + weight : key, optional (default=None) + The edge attribute key to use as weights for + edge current flow betweenness calculations + + Returns + ------- + C : list of sets + Partition of G + + Raises + ------ + NetworkXError + If number_of_sets is <= 0 or number_of_sets > len(G) + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> part = nx.community.edge_current_flow_betweenness_partition(G, 2) + >>> {0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 16, 17, 19, 21} in part + True + >>> {8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33} in part + True + + + See Also + -------- + edge_betweenness_partition + + Notes + ----- + This algorithm is extremely slow, as the recalculation of the edge + current flow betweenness is extremely slow. + + References + ---------- + .. [1] Santo Fortunato 'Community Detection in Graphs' Physical Reports + Volume 486, Issue 3-5 p. 75-174 + http://arxiv.org/abs/0906.0612 + """ + if number_of_sets <= 0: + raise nx.NetworkXError("number_of_sets must be >0") + elif number_of_sets == 1: + return [set(G)] + elif number_of_sets == len(G): + return [{n} for n in G] + elif number_of_sets > len(G): + raise nx.NetworkXError("number_of_sets must be <= len(G)") + + rank = functools.partial( + nx.edge_current_flow_betweenness_centrality, normalized=False, weight=weight + ) + + # current flow requires a connected network so we track the components explicitly + H = G.copy() + partition = list(nx.connected_components(H)) + if len(partition) > 1: + Hcc_subgraphs = [H.subgraph(cc).copy() for cc in partition] + else: + Hcc_subgraphs = [H] + + ranking = {} + for Hcc in Hcc_subgraphs: + ranking.update(rank(Hcc)) + + while len(partition) < number_of_sets: + edge = max(ranking, key=ranking.get) + for cc, Hcc in zip(partition, Hcc_subgraphs): + if edge[0] in cc: + Hcc.remove_edge(*edge) + del ranking[edge] + splitcc_list = list(nx.connected_components(Hcc)) + if len(splitcc_list) > 1: + # there are 2 connected components. split off smaller one + cc_new = min(splitcc_list, key=len) + Hcc_new = Hcc.subgraph(cc_new).copy() + # update edge rankings for Hcc_new + newranks = rank(Hcc_new) + for e, r in newranks.items(): + ranking[e if e in ranking else e[::-1]] = r + # append new cc and Hcc to their lists. + partition.append(cc_new) + Hcc_subgraphs.append(Hcc_new) + + # leave existing cc and Hcc in their lists, but shrink them + Hcc.remove_nodes_from(cc_new) + cc.difference_update(cc_new) + # update edge rankings for Hcc whether it was split or not + newranks = rank(Hcc) + for e, r in newranks.items(): + ranking[e if e in ranking else e[::-1]] = r + break + return partition diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/kclique.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/kclique.py new file mode 100644 index 0000000000000000000000000000000000000000..c72491042046b6f79ba5c7cb4a90ac8822491d84 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/kclique.py @@ -0,0 +1,79 @@ +from collections import defaultdict + +import networkx as nx + +__all__ = ["k_clique_communities"] + + +@nx._dispatchable +def k_clique_communities(G, k, cliques=None): + """Find k-clique communities in graph using the percolation method. + + A k-clique community is the union of all cliques of size k that + can be reached through adjacent (sharing k-1 nodes) k-cliques. + + Parameters + ---------- + G : NetworkX graph + + k : int + Size of smallest clique + + cliques: list or generator + Precomputed cliques (use networkx.find_cliques(G)) + + Returns + ------- + Yields sets of nodes, one for each k-clique community. + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> K5 = nx.convert_node_labels_to_integers(G, first_label=2) + >>> G.add_edges_from(K5.edges()) + >>> c = list(nx.community.k_clique_communities(G, 4)) + >>> sorted(list(c[0])) + [0, 1, 2, 3, 4, 5, 6] + >>> list(nx.community.k_clique_communities(G, 6)) + [] + + References + ---------- + .. [1] Gergely Palla, Imre Derényi, Illés Farkas1, and Tamás Vicsek, + Uncovering the overlapping community structure of complex networks + in nature and society Nature 435, 814-818, 2005, + doi:10.1038/nature03607 + """ + if k < 2: + raise nx.NetworkXError(f"k={k}, k must be greater than 1.") + if cliques is None: + cliques = nx.find_cliques(G) + cliques = [frozenset(c) for c in cliques if len(c) >= k] + + # First index which nodes are in which cliques + membership_dict = defaultdict(list) + for clique in cliques: + for node in clique: + membership_dict[node].append(clique) + + # For each clique, see which adjacent cliques percolate + perc_graph = nx.Graph() + perc_graph.add_nodes_from(cliques) + for clique in cliques: + for adj_clique in _get_adjacent_cliques(clique, membership_dict): + if len(clique.intersection(adj_clique)) >= (k - 1): + perc_graph.add_edge(clique, adj_clique) + + # Connected components of clique graph with perc edges + # are the percolated cliques + for component in nx.connected_components(perc_graph): + yield (frozenset.union(*component)) + + +def _get_adjacent_cliques(clique, membership_dict): + adjacent_cliques = set() + for n in clique: + for adj_clique in membership_dict[n]: + if clique != adj_clique: + adjacent_cliques.add(adj_clique) + return adjacent_cliques diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/label_propagation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/label_propagation.py new file mode 100644 index 0000000000000000000000000000000000000000..7488028655af419c617bd3573b98071e012c4eda --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/label_propagation.py @@ -0,0 +1,338 @@ +""" +Label propagation community detection algorithms. +""" + +from collections import Counter, defaultdict, deque + +import networkx as nx +from networkx.utils import groups, not_implemented_for, py_random_state + +__all__ = [ + "label_propagation_communities", + "asyn_lpa_communities", + "fast_label_propagation_communities", +] + + +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def fast_label_propagation_communities(G, *, weight=None, seed=None): + """Returns communities in `G` as detected by fast label propagation. + + The fast label propagation algorithm is described in [1]_. The algorithm is + probabilistic and the found communities may vary in different executions. + + The algorithm operates as follows. First, the community label of each node is + set to a unique label. The algorithm then repeatedly updates the labels of + the nodes to the most frequent label in their neighborhood. In case of ties, + a random label is chosen from the most frequent labels. + + The algorithm maintains a queue of nodes that still need to be processed. + Initially, all nodes are added to the queue in a random order. Then the nodes + are removed from the queue one by one and processed. If a node updates its label, + all its neighbors that have a different label are added to the queue (if not + already in the queue). The algorithm stops when the queue is empty. + + Parameters + ---------- + G : Graph, DiGraph, MultiGraph, or MultiDiGraph + Any NetworkX graph. + + weight : string, or None (default) + The edge attribute representing a non-negative weight of an edge. If None, + each edge is assumed to have weight one. The weight of an edge is used in + determining the frequency with which a label appears among the neighbors of + a node (edge with weight `w` is equivalent to `w` unweighted edges). + + seed : integer, random_state, or None (default) + Indicator of random number generation state. See :ref:`Randomness`. + + Returns + ------- + communities : iterable + Iterable of communities given as sets of nodes. + + Notes + ----- + Edge directions are ignored for directed graphs. + Edge weights must be non-negative numbers. + + References + ---------- + .. [1] Vincent A. Traag & Lovro Šubelj. "Large network community detection by + fast label propagation." Scientific Reports 13 (2023): 2701. + https://doi.org/10.1038/s41598-023-29610-z + """ + + # Queue of nodes to be processed. + nodes_queue = deque(G) + seed.shuffle(nodes_queue) + + # Set of nodes in the queue. + nodes_set = set(G) + + # Assign unique label to each node. + comms = {node: i for i, node in enumerate(G)} + + while nodes_queue: + # Remove next node from the queue to process. + node = nodes_queue.popleft() + nodes_set.remove(node) + + # Isolated nodes retain their initial label. + if G.degree(node) > 0: + # Compute frequency of labels in node's neighborhood. + label_freqs = _fast_label_count(G, comms, node, weight) + max_freq = max(label_freqs.values()) + + # Always sample new label from most frequent labels. + comm = seed.choice( + [comm for comm in label_freqs if label_freqs[comm] == max_freq] + ) + + if comms[node] != comm: + comms[node] = comm + + # Add neighbors that have different label to the queue. + for nbr in nx.all_neighbors(G, node): + if comms[nbr] != comm and nbr not in nodes_set: + nodes_queue.append(nbr) + nodes_set.add(nbr) + + yield from groups(comms).values() + + +def _fast_label_count(G, comms, node, weight=None): + """Computes the frequency of labels in the neighborhood of a node. + + Returns a dictionary keyed by label to the frequency of that label. + """ + + if weight is None: + # Unweighted (un)directed simple graph. + if not G.is_multigraph(): + label_freqs = Counter(map(comms.get, nx.all_neighbors(G, node))) + + # Unweighted (un)directed multigraph. + else: + label_freqs = defaultdict(int) + for nbr in G[node]: + label_freqs[comms[nbr]] += len(G[node][nbr]) + + if G.is_directed(): + for nbr in G.pred[node]: + label_freqs[comms[nbr]] += len(G.pred[node][nbr]) + + else: + # Weighted undirected simple/multigraph. + label_freqs = defaultdict(float) + for _, nbr, w in G.edges(node, data=weight, default=1): + label_freqs[comms[nbr]] += w + + # Weighted directed simple/multigraph. + if G.is_directed(): + for nbr, _, w in G.in_edges(node, data=weight, default=1): + label_freqs[comms[nbr]] += w + + return label_freqs + + +@py_random_state(2) +@nx._dispatchable(edge_attrs="weight") +def asyn_lpa_communities(G, weight=None, seed=None): + """Returns communities in `G` as detected by asynchronous label + propagation. + + The asynchronous label propagation algorithm is described in + [1]_. The algorithm is probabilistic and the found communities may + vary on different executions. + + The algorithm proceeds as follows. After initializing each node with + a unique label, the algorithm repeatedly sets the label of a node to + be the label that appears most frequently among that nodes + neighbors. The algorithm halts when each node has the label that + appears most frequently among its neighbors. The algorithm is + asynchronous because each node is updated without waiting for + updates on the remaining nodes. + + This generalized version of the algorithm in [1]_ accepts edge + weights. + + Parameters + ---------- + G : Graph + + weight : string + The edge attribute representing the weight of an edge. + If None, each edge is assumed to have weight one. In this + algorithm, the weight of an edge is used in determining the + frequency with which a label appears among the neighbors of a + node: a higher weight means the label appears more often. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + communities : iterable + Iterable of communities given as sets of nodes. + + Notes + ----- + Edge weight attributes must be numerical. + + References + ---------- + .. [1] Raghavan, Usha Nandini, Réka Albert, and Soundar Kumara. "Near + linear time algorithm to detect community structures in large-scale + networks." Physical Review E 76.3 (2007): 036106. + """ + + labels = {n: i for i, n in enumerate(G)} + cont = True + + while cont: + cont = False + nodes = list(G) + seed.shuffle(nodes) + + for node in nodes: + if not G[node]: + continue + + # Get label frequencies among adjacent nodes. + # Depending on the order they are processed in, + # some nodes will be in iteration t and others in t-1, + # making the algorithm asynchronous. + if weight is None: + # initialising a Counter from an iterator of labels is + # faster for getting unweighted label frequencies + label_freq = Counter(map(labels.get, G[node])) + else: + # updating a defaultdict is substantially faster + # for getting weighted label frequencies + label_freq = defaultdict(float) + for _, v, wt in G.edges(node, data=weight, default=1): + label_freq[labels[v]] += wt + + # Get the labels that appear with maximum frequency. + max_freq = max(label_freq.values()) + best_labels = [ + label for label, freq in label_freq.items() if freq == max_freq + ] + + # If the node does not have one of the maximum frequency labels, + # randomly choose one of them and update the node's label. + # Continue the iteration as long as at least one node + # doesn't have a maximum frequency label. + if labels[node] not in best_labels: + labels[node] = seed.choice(best_labels) + cont = True + + yield from groups(labels).values() + + +@not_implemented_for("directed") +@nx._dispatchable +def label_propagation_communities(G): + """Generates community sets determined by label propagation + + Finds communities in `G` using a semi-synchronous label propagation + method [1]_. This method combines the advantages of both the synchronous + and asynchronous models. Not implemented for directed graphs. + + Parameters + ---------- + G : graph + An undirected NetworkX graph. + + Returns + ------- + communities : iterable + A dict_values object that contains a set of nodes for each community. + + Raises + ------ + NetworkXNotImplemented + If the graph is directed + + References + ---------- + .. [1] Cordasco, G., & Gargano, L. (2010, December). Community detection + via semi-synchronous label propagation algorithms. In Business + Applications of Social Network Analysis (BASNA), 2010 IEEE International + Workshop on (pp. 1-8). IEEE. + """ + coloring = _color_network(G) + # Create a unique label for each node in the graph + labeling = {v: k for k, v in enumerate(G)} + while not _labeling_complete(labeling, G): + # Update the labels of every node with the same color. + for color, nodes in coloring.items(): + for n in nodes: + _update_label(n, labeling, G) + + clusters = defaultdict(set) + for node, label in labeling.items(): + clusters[label].add(node) + return clusters.values() + + +def _color_network(G): + """Colors the network so that neighboring nodes all have distinct colors. + + Returns a dict keyed by color to a set of nodes with that color. + """ + coloring = {} # color => set(node) + colors = nx.coloring.greedy_color(G) + for node, color in colors.items(): + if color in coloring: + coloring[color].add(node) + else: + coloring[color] = {node} + return coloring + + +def _labeling_complete(labeling, G): + """Determines whether or not LPA is done. + + Label propagation is complete when all nodes have a label that is + in the set of highest frequency labels amongst its neighbors. + + Nodes with no neighbors are considered complete. + """ + return all( + labeling[v] in _most_frequent_labels(v, labeling, G) for v in G if len(G[v]) > 0 + ) + + +def _most_frequent_labels(node, labeling, G): + """Returns a set of all labels with maximum frequency in `labeling`. + + Input `labeling` should be a dict keyed by node to labels. + """ + if not G[node]: + # Nodes with no neighbors are themselves a community and are labeled + # accordingly, hence the immediate if statement. + return {labeling[node]} + + # Compute the frequencies of all neighbors of node + freqs = Counter(labeling[q] for q in G[node]) + max_freq = max(freqs.values()) + return {label for label, freq in freqs.items() if freq == max_freq} + + +def _update_label(node, labeling, G): + """Updates the label of a node using the Prec-Max tie breaking algorithm + + The algorithm is explained in: 'Community Detection via Semi-Synchronous + Label Propagation Algorithms' Cordasco and Gargano, 2011 + """ + high_labels = _most_frequent_labels(node, labeling, G) + if len(high_labels) == 1: + labeling[node] = high_labels.pop() + elif len(high_labels) > 1: + # Prec-Max + if labeling[node] not in high_labels: + labeling[node] = max(high_labels) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/leiden.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/leiden.py new file mode 100644 index 0000000000000000000000000000000000000000..a37ad6591ceac8337db41ae99c5d55b144db3002 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/leiden.py @@ -0,0 +1,162 @@ +"""Functions for detecting communities based on Leiden Community Detection +algorithm. + +These functions do not have NetworkX implementations. +They may only be run with an installable :doc:`backend ` +that supports them. +""" + +import itertools +from collections import deque + +import networkx as nx +from networkx.utils import not_implemented_for, py_random_state + +__all__ = ["leiden_communities", "leiden_partitions"] + + +@not_implemented_for("directed") +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight", implemented_by_nx=False) +def leiden_communities(G, weight="weight", resolution=1, max_level=None, seed=None): + r"""Find a best partition of `G` using Leiden Community Detection (backend required) + + Leiden Community Detection is an algorithm to extract the community structure + of a network based on modularity optimization. It is an improvement upon the + Louvain Community Detection algorithm. See :any:`louvain_communities`. + + Unlike the Louvain algorithm, it guarantees that communities are well connected in addition + to being faster and uncovering better partitions. [1]_ + + The algorithm works in 3 phases. On the first phase, it adds the nodes to a queue randomly + and assigns every node to be in its own community. For each node it tries to find the + maximum positive modularity gain by moving each node to all of its neighbor communities. + If a node is moved from its community, it adds to the rear of the queue all neighbors of + the node that do not belong to the node’s new community and that are not in the queue. + + The first phase continues until the queue is empty. + + The second phase consists in refining the partition $P$ obtained from the first phase. It starts + with a singleton partition $P_{refined}$. Then it merges nodes locally in $P_{refined}$ within + each community of the partition $P$. Nodes are merged with a community in $P_{refined}$ only if + both are sufficiently well connected to their community in $P$. This means that after the + refinement phase is concluded, communities in $P$ sometimes will have been split into multiple + communities. + + The third phase consists of aggregating the network by building a new network whose nodes are + now the communities found in the second phase. However, the non-refined partition is used to create + an initial partition for the aggregate network. + + Once this phase is complete it is possible to reapply the first and second phases creating bigger + communities with increased modularity. + + The above three phases are executed until no modularity gain is achieved or `max_level` number + of iterations have been performed. + + Parameters + ---------- + G : NetworkX graph + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + resolution : float, optional (default=1) + If resolution is less than 1, the algorithm favors larger communities. + Greater than 1 favors smaller communities. + max_level : int or None, optional (default=None) + The maximum number of levels (steps of the algorithm) to compute. + Must be a positive integer or None. If None, then there is no max + level and the algorithm will run until converged. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + list + A list of disjoint sets (partition of `G`). Each set represents one community. + All communities together contain all the nodes in `G`. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.petersen_graph() + >>> nx.community.leiden_communities(G, backend="example_backend") # doctest: +SKIP + [{2, 3, 5, 7, 8}, {0, 1, 4, 6, 9}] + + Notes + ----- + The order in which the nodes are considered can affect the final output. In the algorithm + the ordering happens using a random shuffle. + + References + ---------- + .. [1] Traag, V.A., Waltman, L. & van Eck, N.J. From Louvain to Leiden: guaranteeing + well-connected communities. Sci Rep 9, 5233 (2019). https://doi.org/10.1038/s41598-019-41695-z + + See Also + -------- + leiden_partitions + :any:`louvain_communities` + """ + partitions = leiden_partitions(G, weight, resolution, seed) + if max_level is not None: + if max_level <= 0: + raise ValueError("max_level argument must be a positive integer or None") + partitions = itertools.islice(partitions, max_level) + final_partition = deque(partitions, maxlen=1) + return final_partition.pop() + + +@not_implemented_for("directed") +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight", implemented_by_nx=False) +def leiden_partitions(G, weight="weight", resolution=1, seed=None): + """Yield partitions for each level of Leiden Community Detection (backend required) + + Leiden Community Detection is an algorithm to extract the community + structure of a network based on modularity optimization. + + The partitions across levels (steps of the algorithm) form a dendrogram + of communities. A dendrogram is a diagram representing a tree and each + level represents a partition of the G graph. The top level contains the + smallest communities and as you traverse to the bottom of the tree the + communities get bigger and the overall modularity increases making + the partition better. + + Each level is generated by executing the three phases of the Leiden Community + Detection algorithm. See :any:`leiden_communities`. + + Parameters + ---------- + G : NetworkX graph + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + resolution : float, optional (default=1) + If resolution is less than 1, the algorithm favors larger communities. + Greater than 1 favors smaller communities. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Yields + ------ + list + A list of disjoint sets (partition of `G`). Each set represents one community. + All communities together contain all the nodes in `G`. The yielded partitions + increase modularity with each iteration. + + References + ---------- + .. [1] Traag, V.A., Waltman, L. & van Eck, N.J. From Louvain to Leiden: guaranteeing + well-connected communities. Sci Rep 9, 5233 (2019). https://doi.org/10.1038/s41598-019-41695-z + + See Also + -------- + leiden_communities + :any:`louvain_partitions` + """ + raise NotImplementedError( + "'leiden_partitions' is not implemented by networkx. " + "Please try a different backend." + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/local.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/local.py new file mode 100644 index 0000000000000000000000000000000000000000..68fc06a0f25823f5be51ff804134a1bfa82a0e07 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/local.py @@ -0,0 +1,220 @@ +""" +Local Community Detection Algorithms + +Local Community Detection (LCD) aims to detected one or a few communities +starting from certain source nodes in the network. This differs from Global +Community Detection (GCD), which aims to partition an entire network into +communities. + +LCD is often useful when only a portion of the graph is known or the +graph is large enough that GCD is infeasable + +[1]_ Gives a good introduction and overview of LCD + +References +---------- +.. [1] Baltsou, Georgia, Konstantinos Christopoulos, and Konstantinos Tsichlas. + Local community detection: A survey. IEEE Access 10 (2022): 110701-110726. + https://doi.org/10.1109/ACCESS.2022.3213980 + + +""" + +__all__ = ["greedy_source_expansion"] + + +def _clauset_greedy_source_expansion(G, *, source, cutoff=None): + if cutoff is None: + cutoff = float("inf") + C = {source} + B = {source} + U = G[source].keys() - C + T = {frozenset([node, nbr]) for node in B for nbr in G.neighbors(node)} + I = {edge for edge in T if all(node in C for node in edge)} + + R_value = 0 + while len(C) < cutoff: + if not U: + break + + max_R = 0 + best_node = None + best_node_B = best_node_T = best_node_I = set() + + for v in U: + R_tmp, B_tmp, T_tmp, I_tmp = _calculate_local_modularity_for_candidate( + G, v, C, B, T, I + ) + if R_tmp > max_R: + max_R = R_tmp + best_node = v + best_node_B = B_tmp + best_node_T = T_tmp + best_node_I = I_tmp + + C = C | {best_node} + U.update(G[best_node].keys() - C) + U.remove(best_node) + B = best_node_B + T = best_node_T + I = best_node_I + if max_R < R_value: + break + R_value = max_R + + return C + + +def _calculate_local_modularity_for_candidate(G, v, C, B, T, I): + """ + Compute the local modularity R and updated variables when adding node v to the community. + + Parameters + ---------- + G : NetworkX graph + The input graph. + v : node + The candidate node to add to the community. + C : set + The current set of community nodes. + B : set + The current set of boundary nodes. + T : set of frozenset + The current set of boundary edges. + I : set of frozenset + The current set of internal boundary edges. + + Returns + ------- + R_tmp : float + The local modularity after adding node v. + B_tmp : set + The updated set of boundary nodes. + T_tmp : set of frozenset + The updated set of boundary edges. + I_tmp : set of frozenset + The updated set of internal boundary edges. + """ + C_tmp = C | {v} + B_tmp = B.copy() + T_tmp = T.copy() + I_tmp = I.copy() + removed_B_nodes = set() + + # Update boundary nodes and edges + for nbr in G[v]: + if nbr not in C_tmp: + # v has nbrs not in the community, so it remains a boundary node + B_tmp.add(v) + # Add edge between v and nbr to boundary edges + T_tmp.add(frozenset([v, nbr])) + + if nbr in B: + # Check if nbr should be removed from boundary nodes + # Go through nbrs nbrs to see if it is still a boundary node + nbr_still_in_B = any(nbr_nbr not in C_tmp for nbr_nbr in G[nbr]) + if not nbr_still_in_B: + B_tmp.remove(nbr) + removed_B_nodes.add(nbr) + + if nbr in C_tmp: + # Add edge between v and nbr to internal edges + I_tmp.add(frozenset([v, nbr])) + + # Remove edges no longer in the boundary + for removed_node in removed_B_nodes: + for removed_node_nbr in G[removed_node]: + if removed_node_nbr not in B_tmp: + T_tmp.discard(frozenset([removed_node_nbr, removed_node])) + I_tmp.discard(frozenset([removed_node_nbr, removed_node])) + + R_tmp = len(I_tmp) / len(T_tmp) if len(T_tmp) > 0 else 1 + return R_tmp, B_tmp, T_tmp, I_tmp + + +ALGORITHMS = { + "clauset": _clauset_greedy_source_expansion, +} + + +def greedy_source_expansion(G, *, source, cutoff=None, method="clauset"): + r"""Find the local community around a source node. + + Find the local community around a source node using Greedy Source + Expansion. Greedy Source Expansion generally identifies a local community + starting from the source node and expands it based on the criteria of the + chosen algorithm. + + The algorithm is specified with the `method` keyword argument. + + * `"clauset"` [1]_ uses local modularity gain to determine local communities. + The algorithm adds nbring nodes that maximize local modularity to the + community iteratively, stopping when no additional nodes improve the modularity + or when a predefined cutoff is reached. + + Local modularity measures the density of edges within a community relative + to the total graph. By focusing on local modularity, the algorithm efficiently + uncovers communities around a specific node without requiring global + optimization over the entire graph. + + The algorithm assumes that the graph $G$ consists of a known community $C$ and + an unknown set of nodes $U$, which are adjacent to $C$ . The boundary of the + community $B$, consists of nodes in $C$ that have at least one nbr in $U$. + + Mathematically, the local modularity is expressed as: + + .. math:: + R = \frac{I}{T} + + where $T$ is the number of edges with one or more endpoints in $B$, and $I$ is the + number of those edges with neither endpoint in $U$. + + Parameters + ---------- + G : NetworkX graph + The input graph. + + source : node + The source node from which the community expansion begins. + + cutoff : int, optional (default=None) + The maximum number of nodes to include in the community. If None, the algorithm + expands until no further modularity gain can be made. + + method : string, optional (default='clauset') + The algorithm to use to carry out greedy source expansion. + Supported options: 'clauset'. Other inputs produce a ValueError + + Returns + ------- + set + A set of nodes representing the local community around the source node. + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> nx.community.greedy_source_expansion(G, source=16) + {16, 0, 4, 5, 6, 10} + + Notes + ----- + This algorithm is designed for detecting local communities around a specific node, + which is useful for large networks where global community detection is computationally + expensive. + + The result of the algorithm may vary based on the structure of the graph, the choice of + the source node, and the presence of ties between nodes during the greedy expansion process. + + References + ---------- + .. [1] Clauset, Aaron. Finding local community structure in networks. + Physical Review E—Statistical, Nonlinear, and Soft Matter Physics 72, no. 2 (2005): 026132. + https://arxiv.org/pdf/physics/0503036 + + """ + try: + algo = ALGORITHMS[method] + except KeyError as e: + raise ValueError(f"{method} is not a valid choice for an algorithm.") from e + + return algo(G, source=source, cutoff=cutoff) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/louvain.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/louvain.py new file mode 100644 index 0000000000000000000000000000000000000000..c8407a8acabadb7df04268d0489337392773e557 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/louvain.py @@ -0,0 +1,384 @@ +"""Functions for detecting communities based on Louvain Community Detection +Algorithm""" + +import itertools +from collections import defaultdict, deque + +import networkx as nx +from networkx.algorithms.community import modularity +from networkx.utils import py_random_state + +__all__ = ["louvain_communities", "louvain_partitions"] + + +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def louvain_communities( + G, weight="weight", resolution=1, threshold=0.0000001, max_level=None, seed=None +): + r"""Find the best partition of a graph using the Louvain Community Detection + Algorithm. + + Louvain Community Detection Algorithm is a simple method to extract the community + structure of a network. This is a heuristic method based on modularity optimization. [1]_ + + The algorithm works in 2 steps. On the first step it assigns every node to be + in its own community and then for each node it tries to find the maximum positive + modularity gain by moving each node to all of its neighbor communities. If no positive + gain is achieved the node remains in its original community. + + The modularity gain obtained by moving an isolated node $i$ into a community $C$ can + easily be calculated by the following formula (combining [1]_ [2]_ and some algebra): + + .. math:: + \Delta Q = \frac{k_{i,in}}{2m} - \gamma\frac{ \Sigma_{tot} \cdot k_i}{2m^2} + + where $m$ is the size of the graph, $k_{i,in}$ is the sum of the weights of the links + from $i$ to nodes in $C$, $k_i$ is the sum of the weights of the links incident to node $i$, + $\Sigma_{tot}$ is the sum of the weights of the links incident to nodes in $C$ and $\gamma$ + is the resolution parameter. + + For the directed case the modularity gain can be computed using this formula according to [3]_ + + .. math:: + \Delta Q = \frac{k_{i,in}}{m} + - \gamma\frac{k_i^{out} \cdot\Sigma_{tot}^{in} + k_i^{in} \cdot \Sigma_{tot}^{out}}{m^2} + + where $k_i^{out}$, $k_i^{in}$ are the outer and inner weighted degrees of node $i$ and + $\Sigma_{tot}^{in}$, $\Sigma_{tot}^{out}$ are the sum of in-going and out-going links incident + to nodes in $C$. + + The first phase continues until no individual move can improve the modularity. + + The second phase consists in building a new network whose nodes are now the communities + found in the first phase. To do so, the weights of the links between the new nodes are given by + the sum of the weight of the links between nodes in the corresponding two communities. Once this + phase is complete it is possible to reapply the first phase creating bigger communities with + increased modularity. + + The above two phases are executed until no modularity gain is achieved (or is less than + the `threshold`, or until `max_levels` is reached). + + Be careful with self-loops in the input graph. These are treated as + previously reduced communities -- as if the process had been started + in the middle of the algorithm. Large self-loop edge weights thus + represent strong communities and in practice may be hard to add + other nodes to. If your input graph edge weights for self-loops + do not represent already reduced communities you may want to remove + the self-loops before inputting that graph. + + Parameters + ---------- + G : NetworkX graph + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + resolution : float, optional (default=1) + If resolution is less than 1, the algorithm favors larger communities. + Greater than 1 favors smaller communities + threshold : float, optional (default=0.0000001) + Modularity gain threshold for each level. If the gain of modularity + between 2 levels of the algorithm is less than the given threshold + then the algorithm stops and returns the resulting communities. + max_level : int or None, optional (default=None) + The maximum number of levels (steps of the algorithm) to compute. + Must be a positive integer or None. If None, then there is no max + level and the threshold parameter determines the stopping condition. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + list + A list of sets (partition of `G`). Each set represents one community and contains + all the nodes that constitute it. + + Examples + -------- + >>> import networkx as nx + >>> G = nx.petersen_graph() + >>> nx.community.louvain_communities(G, seed=123) + [{0, 4, 5, 7, 9}, {1, 2, 3, 6, 8}] + + Notes + ----- + The order in which the nodes are considered can affect the final output. In the algorithm + the ordering happens using a random shuffle. + + References + ---------- + .. [1] Blondel, V.D. et al. Fast unfolding of communities in + large networks. J. Stat. Mech 10008, 1-12(2008). https://doi.org/10.1088/1742-5468/2008/10/P10008 + .. [2] Traag, V.A., Waltman, L. & van Eck, N.J. From Louvain to Leiden: guaranteeing + well-connected communities. Sci Rep 9, 5233 (2019). https://doi.org/10.1038/s41598-019-41695-z + .. [3] Nicolas Dugué, Anthony Perez. Directed Louvain : maximizing modularity in directed networks. + [Research Report] Université d’Orléans. 2015. hal-01231784. https://hal.archives-ouvertes.fr/hal-01231784 + + See Also + -------- + louvain_partitions + :any:`leiden_communities` + """ + + partitions = louvain_partitions(G, weight, resolution, threshold, seed) + if max_level is not None: + if max_level <= 0: + raise ValueError("max_level argument must be a positive integer or None") + partitions = itertools.islice(partitions, max_level) + final_partition = deque(partitions, maxlen=1) + return final_partition.pop() + + +@py_random_state("seed") +@nx._dispatchable(edge_attrs="weight") +def louvain_partitions( + G, weight="weight", resolution=1, threshold=0.0000001, seed=None +): + """Yield partitions for each level of the Louvain Community Detection Algorithm + + Louvain Community Detection Algorithm is a simple method to extract the community + structure of a network. This is a heuristic method based on modularity optimization. [1]_ + + The partitions at each level (step of the algorithm) form a dendrogram of communities. + A dendrogram is a diagram representing a tree and each level represents + a partition of the G graph. The top level contains the smallest communities + and as you traverse to the bottom of the tree the communities get bigger + and the overall modularity increases making the partition better. + + Each level is generated by executing the two phases of the Louvain Community + Detection Algorithm. + + Be careful with self-loops in the input graph. These are treated as + previously reduced communities -- as if the process had been started + in the middle of the algorithm. Large self-loop edge weights thus + represent strong communities and in practice may be hard to add + other nodes to. If your input graph edge weights for self-loops + do not represent already reduced communities you may want to remove + the self-loops before inputting that graph. + + Parameters + ---------- + G : NetworkX graph + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + resolution : float, optional (default=1) + If resolution is less than 1, the algorithm favors larger communities. + Greater than 1 favors smaller communities + threshold : float, optional (default=0.0000001) + Modularity gain threshold for each level. If the gain of modularity + between 2 levels of the algorithm is less than the given threshold + then the algorithm stops and returns the resulting communities. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Yields + ------ + list + A list of sets (partition of `G`). Each set represents one community and contains + all the nodes that constitute it. + + References + ---------- + .. [1] Blondel, V.D. et al. Fast unfolding of communities in + large networks. J. Stat. Mech 10008, 1-12(2008) + + See Also + -------- + louvain_communities + :any:`leiden_partitions` + """ + + partition = [{u} for u in G.nodes()] + if nx.is_empty(G): + yield partition + return + mod = modularity(G, partition, resolution=resolution, weight=weight) + is_directed = G.is_directed() + if G.is_multigraph(): + graph = _convert_multigraph(G, weight, is_directed) + else: + graph = G.__class__() + graph.add_nodes_from(G) + graph.add_weighted_edges_from(G.edges(data=weight, default=1)) + + m = graph.size(weight="weight") + partition, inner_partition, improvement = _one_level( + graph, m, partition, resolution, is_directed, seed + ) + improvement = True + while improvement: + # gh-5901 protect the sets in the yielded list from further manipulation here + yield [s.copy() for s in partition] + new_mod = modularity( + graph, inner_partition, resolution=resolution, weight="weight" + ) + if new_mod - mod <= threshold: + return + mod = new_mod + graph = _gen_graph(graph, inner_partition) + partition, inner_partition, improvement = _one_level( + graph, m, partition, resolution, is_directed, seed + ) + + +def _one_level(G, m, partition, resolution=1, is_directed=False, seed=None): + """Calculate one level of the Louvain partitions tree + + Parameters + ---------- + G : NetworkX Graph/DiGraph + The graph from which to detect communities + m : number + The size of the graph `G`. + partition : list of sets of nodes + A valid partition of the graph `G` + resolution : positive number + The resolution parameter for computing the modularity of a partition + is_directed : bool + True if `G` is a directed graph. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + """ + node2com = {u: i for i, u in enumerate(G.nodes())} + inner_partition = [{u} for u in G.nodes()] + if is_directed: + in_degrees = dict(G.in_degree(weight="weight")) + out_degrees = dict(G.out_degree(weight="weight")) + Stot_in = list(in_degrees.values()) + Stot_out = list(out_degrees.values()) + # Calculate weights for both in and out neighbors without considering self-loops + nbrs = {} + for u in G: + nbrs[u] = defaultdict(float) + for _, n, wt in G.out_edges(u, data="weight"): + if u != n: + nbrs[u][n] += wt + for n, _, wt in G.in_edges(u, data="weight"): + if u != n: + nbrs[u][n] += wt + else: + degrees = dict(G.degree(weight="weight")) + Stot = list(degrees.values()) + nbrs = {u: {v: data["weight"] for v, data in G[u].items() if v != u} for u in G} + rand_nodes = list(G.nodes) + seed.shuffle(rand_nodes) + nb_moves = 1 + improvement = False + while nb_moves > 0: + nb_moves = 0 + for u in rand_nodes: + best_mod = 0 + best_com = node2com[u] + weights2com = _neighbor_weights(nbrs[u], node2com) + if is_directed: + in_degree = in_degrees[u] + out_degree = out_degrees[u] + Stot_in[best_com] -= in_degree + Stot_out[best_com] -= out_degree + remove_cost = ( + -weights2com[best_com] / m + + resolution + * (out_degree * Stot_in[best_com] + in_degree * Stot_out[best_com]) + / m**2 + ) + else: + degree = degrees[u] + Stot[best_com] -= degree + remove_cost = -weights2com[best_com] / m + resolution * ( + Stot[best_com] * degree + ) / (2 * m**2) + for nbr_com, wt in weights2com.items(): + if is_directed: + gain = ( + remove_cost + + wt / m + - resolution + * ( + out_degree * Stot_in[nbr_com] + + in_degree * Stot_out[nbr_com] + ) + / m**2 + ) + else: + gain = ( + remove_cost + + wt / m + - resolution * (Stot[nbr_com] * degree) / (2 * m**2) + ) + if gain > best_mod: + best_mod = gain + best_com = nbr_com + if is_directed: + Stot_in[best_com] += in_degree + Stot_out[best_com] += out_degree + else: + Stot[best_com] += degree + if best_com != node2com[u]: + com = G.nodes[u].get("nodes", {u}) + partition[node2com[u]].difference_update(com) + inner_partition[node2com[u]].remove(u) + partition[best_com].update(com) + inner_partition[best_com].add(u) + improvement = True + nb_moves += 1 + node2com[u] = best_com + partition = list(filter(len, partition)) + inner_partition = list(filter(len, inner_partition)) + return partition, inner_partition, improvement + + +def _neighbor_weights(nbrs, node2com): + """Calculate weights between node and its neighbor communities. + + Parameters + ---------- + nbrs : dictionary + Dictionary with nodes' neighbors as keys and their edge weight as value. + node2com : dictionary + Dictionary with all graph's nodes as keys and their community index as value. + + """ + weights = defaultdict(float) + for nbr, wt in nbrs.items(): + weights[node2com[nbr]] += wt + return weights + + +def _gen_graph(G, partition): + """Generate a new graph based on the partitions of a given graph""" + H = G.__class__() + node2com = {} + for i, part in enumerate(partition): + nodes = set() + for node in part: + node2com[node] = i + nodes.update(G.nodes[node].get("nodes", {node})) + H.add_node(i, nodes=nodes) + + for node1, node2, wt in G.edges(data=True): + wt = wt["weight"] + com1 = node2com[node1] + com2 = node2com[node2] + temp = H.get_edge_data(com1, com2, {"weight": 0})["weight"] + H.add_edge(com1, com2, weight=wt + temp) + return H + + +def _convert_multigraph(G, weight, is_directed): + """Convert a Multigraph to normal Graph""" + if is_directed: + H = nx.DiGraph() + else: + H = nx.Graph() + H.add_nodes_from(G) + for u, v, wt in G.edges(data=weight, default=1): + if H.has_edge(u, v): + H[u][v]["weight"] += wt + else: + H.add_edge(u, v, weight=wt) + return H diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/lukes.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/lukes.py new file mode 100644 index 0000000000000000000000000000000000000000..08dd7cd52ff414c1397e3effea504853f3c9caf7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/lukes.py @@ -0,0 +1,227 @@ +"""Lukes Algorithm for exact optimal weighted tree partitioning.""" + +from copy import deepcopy +from functools import lru_cache +from random import choice + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = ["lukes_partitioning"] + +D_EDGE_W = "weight" +D_EDGE_VALUE = 1.0 +D_NODE_W = "weight" +D_NODE_VALUE = 1 +PKEY = "partitions" +CLUSTER_EVAL_CACHE_SIZE = 2048 + + +def _split_n_from(n, min_size_of_first_part): + # splits j in two parts of which the first is at least + # the second argument + assert n >= min_size_of_first_part + for p1 in range(min_size_of_first_part, n + 1): + yield p1, n - p1 + + +@nx._dispatchable(node_attrs="node_weight", edge_attrs="edge_weight") +def lukes_partitioning(G, max_size, node_weight=None, edge_weight=None): + """Optimal partitioning of a weighted tree using the Lukes algorithm. + + This algorithm partitions a connected, acyclic graph featuring integer + node weights and float edge weights. The resulting clusters are such + that the total weight of the nodes in each cluster does not exceed + max_size and that the weight of the edges that are cut by the partition + is minimum. The algorithm is based on [1]_. + + Parameters + ---------- + G : NetworkX graph + + max_size : int + Maximum weight a partition can have in terms of sum of + node_weight for all nodes in the partition + + edge_weight : key + Edge data key to use as weight. If None, the weights are all + set to one. + + node_weight : key + Node data key to use as weight. If None, the weights are all + set to one. The data must be int. + + Returns + ------- + partition : list + A list of sets of nodes representing the clusters of the + partition. + + Raises + ------ + NotATree + If G is not a tree. + TypeError + If any of the values of node_weight is not int. + + References + ---------- + .. [1] Lukes, J. A. (1974). + "Efficient Algorithm for the Partitioning of Trees." + IBM Journal of Research and Development, 18(3), 217–224. + + """ + # First sanity check and tree preparation + if not nx.is_tree(G): + raise nx.NotATree("lukes_partitioning works only on trees") + else: + if nx.is_directed(G): + root = [n for n, d in G.in_degree() if d == 0] + assert len(root) == 1 + root = root[0] + t_G = deepcopy(G) + else: + root = choice(list(G.nodes)) + # this has the desirable side effect of not inheriting attributes + t_G = nx.dfs_tree(G, root) + + # Since we do not want to screw up the original graph, + # if we have a blank attribute, we make a deepcopy + if edge_weight is None or node_weight is None: + safe_G = deepcopy(G) + if edge_weight is None: + nx.set_edge_attributes(safe_G, D_EDGE_VALUE, D_EDGE_W) + edge_weight = D_EDGE_W + if node_weight is None: + nx.set_node_attributes(safe_G, D_NODE_VALUE, D_NODE_W) + node_weight = D_NODE_W + else: + safe_G = G + + # Second sanity check + # The values of node_weight MUST BE int. + # I cannot see any room for duck typing without incurring serious + # danger of subtle bugs. + all_n_attr = nx.get_node_attributes(safe_G, node_weight).values() + for x in all_n_attr: + if not isinstance(x, int): + raise TypeError( + "lukes_partitioning needs integer " + f"values for node_weight ({node_weight})" + ) + + # SUBROUTINES ----------------------- + # these functions are defined here for two reasons: + # - brevity: we can leverage global "safe_G" + # - caching: signatures are hashable + + @not_implemented_for("undirected") + # this is intended to be called only on t_G + def _leaves(gr): + for x in gr.nodes: + if not nx.descendants(gr, x): + yield x + + @not_implemented_for("undirected") + def _a_parent_of_leaves_only(gr): + tleaves = set(_leaves(gr)) + for n in set(gr.nodes) - tleaves: + if all(x in tleaves for x in nx.descendants(gr, n)): + return n + + @lru_cache(CLUSTER_EVAL_CACHE_SIZE) + def _value_of_cluster(cluster): + valid_edges = [e for e in safe_G.edges if e[0] in cluster and e[1] in cluster] + return sum(safe_G.edges[e][edge_weight] for e in valid_edges) + + def _value_of_partition(partition): + return sum(_value_of_cluster(frozenset(c)) for c in partition) + + @lru_cache(CLUSTER_EVAL_CACHE_SIZE) + def _weight_of_cluster(cluster): + return sum(safe_G.nodes[n][node_weight] for n in cluster) + + def _pivot(partition, node): + ccx = [c for c in partition if node in c] + assert len(ccx) == 1 + return ccx[0] + + def _concatenate_or_merge(partition_1, partition_2, x, i, ref_weight): + ccx = _pivot(partition_1, x) + cci = _pivot(partition_2, i) + merged_xi = ccx.union(cci) + + # We first check if we can do the merge. + # If so, we do the actual calculations, otherwise we concatenate + if _weight_of_cluster(frozenset(merged_xi)) <= ref_weight: + cp1 = list(filter(lambda x: x != ccx, partition_1)) + cp2 = list(filter(lambda x: x != cci, partition_2)) + + option_2 = [merged_xi] + cp1 + cp2 + return option_2, _value_of_partition(option_2) + else: + option_1 = partition_1 + partition_2 + return option_1, _value_of_partition(option_1) + + # INITIALIZATION ----------------------- + leaves = set(_leaves(t_G)) + for lv in leaves: + t_G.nodes[lv][PKEY] = {} + slot = safe_G.nodes[lv][node_weight] + t_G.nodes[lv][PKEY][slot] = [{lv}] + t_G.nodes[lv][PKEY][0] = [{lv}] + + for inner in [x for x in t_G.nodes if x not in leaves]: + t_G.nodes[inner][PKEY] = {} + slot = safe_G.nodes[inner][node_weight] + t_G.nodes[inner][PKEY][slot] = [{inner}] + nx._clear_cache(t_G) + + # CORE ALGORITHM ----------------------- + while True: + x_node = _a_parent_of_leaves_only(t_G) + weight_of_x = safe_G.nodes[x_node][node_weight] + best_value = 0 + best_partition = None + bp_buffer = {} + x_descendants = nx.descendants(t_G, x_node) + for i_node in x_descendants: + for j in range(weight_of_x, max_size + 1): + for a, b in _split_n_from(j, weight_of_x): + if ( + a not in t_G.nodes[x_node][PKEY] + or b not in t_G.nodes[i_node][PKEY] + ): + # it's not possible to form this particular weight sum + continue + + part1 = t_G.nodes[x_node][PKEY][a] + part2 = t_G.nodes[i_node][PKEY][b] + part, value = _concatenate_or_merge(part1, part2, x_node, i_node, j) + + if j not in bp_buffer or bp_buffer[j][1] < value: + # we annotate in the buffer the best partition for j + bp_buffer[j] = part, value + + # we also keep track of the overall best partition + if best_value <= value: + best_value = value + best_partition = part + + # as illustrated in Lukes, once we finished a child, we can + # discharge the partitions we found into the graph + # (the key phrase is make all x == x') + # so that they are used by the subsequent children + for w, (best_part_for_vl, vl) in bp_buffer.items(): + t_G.nodes[x_node][PKEY][w] = best_part_for_vl + bp_buffer.clear() + + # the absolute best partition for this node + # across all weights has to be stored at 0 + t_G.nodes[x_node][PKEY][0] = best_partition + t_G.remove_nodes_from(x_descendants) + + if x_node == root: + # the 0-labeled partition of root + # is the optimal one for the whole tree + return t_G.nodes[root][PKEY][0] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/modularity_max.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/modularity_max.py new file mode 100644 index 0000000000000000000000000000000000000000..ece44c4b839d3ddaffdde5554c09bd9a9a6476f0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/modularity_max.py @@ -0,0 +1,452 @@ +"""Functions for detecting communities based on modularity.""" + +import random +from collections import defaultdict +from copy import deepcopy + +import networkx as nx +from networkx.algorithms.community.quality import modularity +from networkx.utils.mapped_queue import MappedQueue + +__all__ = [ + "greedy_modularity_communities", + "naive_greedy_modularity_communities", +] + + +def _greedy_modularity_communities_generator(G, weight=None, resolution=1): + r"""Yield community partitions of G and the modularity change at each step. + + This function performs Clauset-Newman-Moore greedy modularity maximization [2]_ + At each step of the process it yields the change in modularity that will occur in + the next step followed by yielding the new community partition after that step. + + Greedy modularity maximization begins with each node in its own community + and repeatedly joins the pair of communities that lead to the largest + modularity until one community contains all nodes (the partition has one set). + + This function maximizes the generalized modularity, where `resolution` + is the resolution parameter, often expressed as $\gamma$. + See :func:`~networkx.algorithms.community.quality.modularity`. + + Parameters + ---------- + G : NetworkX graph + + weight : string or None, optional (default=None) + The name of an edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + resolution : float (default=1) + If resolution is less than 1, modularity favors larger communities. + Greater than 1 favors smaller communities. + + Yields + ------ + Alternating yield statements produce the following two objects: + + communities: dict_values + A dict_values of frozensets of nodes, one for each community. + This represents a partition of the nodes of the graph into communities. + The first yield is the partition with each node in its own community. + + dq: float + The change in modularity when merging the next two communities + that leads to the largest modularity. + + See Also + -------- + modularity + + References + ---------- + .. [1] Newman, M. E. J. "Networks: An Introduction", page 224 + Oxford University Press 2011. + .. [2] Clauset, A., Newman, M. E., & Moore, C. + "Finding community structure in very large networks." + Physical Review E 70(6), 2004. + .. [3] Reichardt and Bornholdt "Statistical Mechanics of Community + Detection" Phys. Rev. E74, 2006. + .. [4] Newman, M. E. J."Analysis of weighted networks" + Physical Review E 70(5 Pt 2):056131, 2004. + """ + directed = G.is_directed() + N = G.number_of_nodes() + + # Count edges (or the sum of edge-weights for weighted graphs) + m = G.size(weight) + q0 = 1 / m + + # Calculate degrees (notation from the papers) + # a : the fraction of (weighted) out-degree for each node + # b : the fraction of (weighted) in-degree for each node + if directed: + a = {node: deg_out * q0 for node, deg_out in G.out_degree(weight=weight)} + b = {node: deg_in * q0 for node, deg_in in G.in_degree(weight=weight)} + else: + a = b = {node: deg * q0 * 0.5 for node, deg in G.degree(weight=weight)} + + # this preliminary step collects the edge weights for each node pair + # It handles multigraph and digraph and works fine for graph. + dq_dict = defaultdict(lambda: defaultdict(float)) + for u, v, wt in G.edges(data=weight, default=1): + if u == v: + continue + dq_dict[u][v] += wt + dq_dict[v][u] += wt + + # now scale and subtract the expected edge-weights term + for u, nbrdict in dq_dict.items(): + for v, wt in nbrdict.items(): + dq_dict[u][v] = q0 * wt - resolution * (a[u] * b[v] + b[u] * a[v]) + + # Use -dq to get a max_heap instead of a min_heap + # dq_heap holds a heap for each node's neighbors + dq_heap = {u: MappedQueue({(u, v): -dq for v, dq in dq_dict[u].items()}) for u in G} + # H -> all_dq_heap holds a heap with the best items for each node + H = MappedQueue([dq_heap[n].heap[0] for n in G if len(dq_heap[n]) > 0]) + + # Initialize single-node communities + communities = {n: frozenset([n]) for n in G} + yield communities.values() + + # Merge the two communities that lead to the largest modularity + while len(H) > 1: + # Find best merge + # Remove from heap of row maxes + # Ties will be broken by choosing the pair with lowest min community id + try: + negdq, u, v = H.pop() + except IndexError: + break + dq = -negdq + yield dq + # Remove best merge from row u heap + dq_heap[u].pop() + # Push new row max onto H + if len(dq_heap[u]) > 0: + H.push(dq_heap[u].heap[0]) + # If this element was also at the root of row v, we need to remove the + # duplicate entry from H + if dq_heap[v].heap[0] == (v, u): + H.remove((v, u)) + # Remove best merge from row v heap + dq_heap[v].remove((v, u)) + # Push new row max onto H + if len(dq_heap[v]) > 0: + H.push(dq_heap[v].heap[0]) + else: + # Duplicate wasn't in H, just remove from row v heap + dq_heap[v].remove((v, u)) + + # Perform merge + communities[v] = frozenset(communities[u] | communities[v]) + del communities[u] + + # Get neighbor communities connected to the merged communities + u_nbrs = set(dq_dict[u]) + v_nbrs = set(dq_dict[v]) + all_nbrs = (u_nbrs | v_nbrs) - {u, v} + both_nbrs = u_nbrs & v_nbrs + # Update dq for merge of u into v + for w in all_nbrs: + # Calculate new dq value + if w in both_nbrs: + dq_vw = dq_dict[v][w] + dq_dict[u][w] + elif w in v_nbrs: + dq_vw = dq_dict[v][w] - resolution * (a[u] * b[w] + a[w] * b[u]) + else: # w in u_nbrs + dq_vw = dq_dict[u][w] - resolution * (a[v] * b[w] + a[w] * b[v]) + # Update rows v and w + for row, col in [(v, w), (w, v)]: + dq_heap_row = dq_heap[row] + # Update dict for v,w only (u is removed below) + dq_dict[row][col] = dq_vw + # Save old max of per-row heap + if len(dq_heap_row) > 0: + d_oldmax = dq_heap_row.heap[0] + else: + d_oldmax = None + # Add/update heaps + d = (row, col) + d_negdq = -dq_vw + # Save old value for finding heap index + if w in v_nbrs: + # Update existing element in per-row heap + dq_heap_row.update(d, d, priority=d_negdq) + else: + # We're creating a new nonzero element, add to heap + dq_heap_row.push(d, priority=d_negdq) + # Update heap of row maxes if necessary + if d_oldmax is None: + # No entries previously in this row, push new max + H.push(d, priority=d_negdq) + else: + # We've updated an entry in this row, has the max changed? + row_max = dq_heap_row.heap[0] + if d_oldmax != row_max or d_oldmax.priority != row_max.priority: + H.update(d_oldmax, row_max) + + # Remove row/col u from dq_dict matrix + for w in dq_dict[u]: + # Remove from dict + dq_old = dq_dict[w][u] + del dq_dict[w][u] + # Remove from heaps if we haven't already + if w != v: + # Remove both row and column + for row, col in [(w, u), (u, w)]: + dq_heap_row = dq_heap[row] + # Check if replaced dq is row max + d_old = (row, col) + if dq_heap_row.heap[0] == d_old: + # Update per-row heap and heap of row maxes + dq_heap_row.remove(d_old) + H.remove(d_old) + # Update row max + if len(dq_heap_row) > 0: + H.push(dq_heap_row.heap[0]) + else: + # Only update per-row heap + dq_heap_row.remove(d_old) + + del dq_dict[u] + # Mark row u as deleted, but keep placeholder + dq_heap[u] = MappedQueue() + # Merge u into v and update a + a[v] += a[u] + a[u] = 0 + if directed: + b[v] += b[u] + b[u] = 0 + + yield communities.values() + + +@nx._dispatchable(edge_attrs="weight") +def greedy_modularity_communities( + G, + weight=None, + resolution=1, + cutoff=1, + best_n=None, +): + r"""Find communities in G using greedy modularity maximization. + + This function uses Clauset-Newman-Moore greedy modularity maximization [2]_ + to find the community partition with the largest modularity. + + Greedy modularity maximization begins with each node in its own community + and repeatedly joins the pair of communities that lead to the largest + modularity until no further increase in modularity is possible (a maximum). + Two keyword arguments adjust the stopping condition. `cutoff` is a lower + limit on the number of communities so you can stop the process before + reaching a maximum (used to save computation time). `best_n` is an upper + limit on the number of communities so you can make the process continue + until at most n communities remain even if the maximum modularity occurs + for more. To obtain exactly n communities, set both `cutoff` and `best_n` to n. + + This function maximizes the generalized modularity, where `resolution` + is the resolution parameter, often expressed as $\gamma$. + See :func:`~networkx.algorithms.community.quality.modularity`. + + Parameters + ---------- + G : NetworkX graph + + weight : string or None, optional (default=None) + The name of an edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + resolution : float, optional (default=1) + If resolution is less than 1, modularity favors larger communities. + Greater than 1 favors smaller communities. + + cutoff : int, optional (default=1) + A minimum number of communities below which the merging process stops. + The process stops at this number of communities even if modularity + is not maximized. The goal is to let the user stop the process early. + The process stops before the cutoff if it finds a maximum of modularity. + + best_n : int or None, optional (default=None) + A maximum number of communities above which the merging process will + not stop. This forces community merging to continue after modularity + starts to decrease until `best_n` communities remain. + If ``None``, don't force it to continue beyond a maximum. + + Raises + ------ + ValueError : If the `cutoff` or `best_n` value is not in the range + ``[1, G.number_of_nodes()]``, or if `best_n` < `cutoff`. + + Returns + ------- + communities: list + A list of frozensets of nodes, one for each community. + Sorted by length with largest communities first. + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> c = nx.community.greedy_modularity_communities(G) + >>> sorted(c[0]) + [8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33] + + See Also + -------- + modularity + + References + ---------- + .. [1] Newman, M. E. J. "Networks: An Introduction", page 224 + Oxford University Press 2011. + .. [2] Clauset, A., Newman, M. E., & Moore, C. + "Finding community structure in very large networks." + Physical Review E 70(6), 2004. + .. [3] Reichardt and Bornholdt "Statistical Mechanics of Community + Detection" Phys. Rev. E74, 2006. + .. [4] Newman, M. E. J."Analysis of weighted networks" + Physical Review E 70(5 Pt 2):056131, 2004. + """ + if not G.size(): + return [{n} for n in G] + + if (cutoff < 1) or (cutoff > G.number_of_nodes()): + raise ValueError(f"cutoff must be between 1 and {len(G)}. Got {cutoff}.") + if best_n is not None: + if (best_n < 1) or (best_n > G.number_of_nodes()): + raise ValueError(f"best_n must be between 1 and {len(G)}. Got {best_n}.") + if best_n < cutoff: + raise ValueError(f"Must have best_n >= cutoff. Got {best_n} < {cutoff}") + if best_n == 1: + return [set(G)] + else: + best_n = G.number_of_nodes() + + # retrieve generator object to construct output + community_gen = _greedy_modularity_communities_generator( + G, weight=weight, resolution=resolution + ) + + # construct the first best community + communities = next(community_gen) + + # continue merging communities until one of the breaking criteria is satisfied + while len(communities) > cutoff: + try: + dq = next(community_gen) + # StopIteration occurs when communities are the connected components + except StopIteration: + communities = sorted(communities, key=len, reverse=True) + # if best_n requires more merging, merge big sets for highest modularity + while len(communities) > best_n: + comm1, comm2, *rest = communities + communities = [comm1 ^ comm2] + communities.extend(rest) + return communities + + # keep going unless max_mod is reached or best_n says to merge more + if dq < 0 and len(communities) <= best_n: + break + communities = next(community_gen) + + return sorted(communities, key=len, reverse=True) + + +@nx.utils.not_implemented_for("directed") +@nx.utils.not_implemented_for("multigraph") +@nx._dispatchable(edge_attrs="weight") +def naive_greedy_modularity_communities(G, resolution=1, weight=None): + r"""Find communities in G using greedy modularity maximization. + + This implementation is O(n^4), much slower than alternatives, but it is + provided as an easy-to-understand reference implementation. + + Greedy modularity maximization begins with each node in its own community + and joins the pair of communities that most increases modularity until no + such pair exists. + + This function maximizes the generalized modularity, where `resolution` + is the resolution parameter, often expressed as $\gamma$. + See :func:`~networkx.algorithms.community.quality.modularity`. + + Parameters + ---------- + G : NetworkX graph + Graph must be simple and undirected. + + resolution : float (default=1) + If resolution is less than 1, modularity favors larger communities. + Greater than 1 favors smaller communities. + + weight : string or None, optional (default=None) + The name of an edge attribute that holds the numerical value used + as a weight. If None, then each edge has weight 1. + The degree is the sum of the edge weights adjacent to the node. + + Returns + ------- + list + A list of sets of nodes, one for each community. + Sorted by length with largest communities first. + + Examples + -------- + >>> G = nx.karate_club_graph() + >>> c = nx.community.naive_greedy_modularity_communities(G) + >>> sorted(c[0]) + [8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33] + + See Also + -------- + greedy_modularity_communities + modularity + """ + # First create one community for each node + communities = [frozenset([u]) for u in G.nodes()] + # Track merges + merges = [] + # Greedily merge communities until no improvement is possible + old_modularity = None + new_modularity = modularity(G, communities, resolution=resolution, weight=weight) + while old_modularity is None or new_modularity > old_modularity: + # Save modularity for comparison + old_modularity = new_modularity + # Find best pair to merge + trial_communities = list(communities) + to_merge = None + for i, u in enumerate(communities): + for j, v in enumerate(communities): + # Skip i==j and empty communities + if j <= i or len(u) == 0 or len(v) == 0: + continue + # Merge communities u and v + trial_communities[j] = u | v + trial_communities[i] = frozenset([]) + trial_modularity = modularity( + G, trial_communities, resolution=resolution, weight=weight + ) + if trial_modularity >= new_modularity: + # Check if strictly better or tie + if trial_modularity > new_modularity: + # Found new best, save modularity and group indexes + new_modularity = trial_modularity + to_merge = (i, j, new_modularity - old_modularity) + elif to_merge and min(i, j) < min(to_merge[0], to_merge[1]): + # Break ties by choosing pair with lowest min id + new_modularity = trial_modularity + to_merge = (i, j, new_modularity - old_modularity) + # Un-merge + trial_communities[i] = u + trial_communities[j] = v + if to_merge is not None: + # If the best merge improves modularity, use it + merges.append(to_merge) + i, j, dq = to_merge + u, v = communities[i], communities[j] + communities[j] = u | v + communities[i] = frozenset([]) + # Remove empty communities and sort + return sorted((c for c in communities if len(c) > 0), key=len, reverse=True) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/quality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/quality.py new file mode 100644 index 0000000000000000000000000000000000000000..4b4dcbba1ce88f55f667cbe010fa89bf71cb99db --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/quality.py @@ -0,0 +1,347 @@ +"""Functions for measuring the quality of a partition (into +communities). + +""" + +from itertools import combinations + +import networkx as nx +from networkx import NetworkXError +from networkx.algorithms.community.community_utils import is_partition +from networkx.utils.decorators import argmap + +__all__ = ["modularity", "partition_quality"] + + +class NotAPartition(NetworkXError): + """Raised if a given collection is not a partition.""" + + def __init__(self, G, collection): + msg = f"{collection} is not a valid partition of the graph {G}" + super().__init__(msg) + + +def _require_partition(G, partition): + """Decorator to check that a valid partition is input to a function + + Raises :exc:`networkx.NetworkXError` if the partition is not valid. + + This decorator should be used on functions whose first two arguments + are a graph and a partition of the nodes of that graph (in that + order):: + + >>> @require_partition + ... def foo(G, partition): + ... print("partition is valid!") + ... + >>> G = nx.complete_graph(5) + >>> partition = [{0, 1}, {2, 3}, {4}] + >>> foo(G, partition) + partition is valid! + >>> partition = [{0}, {2, 3}, {4}] + >>> foo(G, partition) + Traceback (most recent call last): + ... + networkx.exception.NetworkXError: `partition` is not a valid partition of the nodes of G + >>> partition = [{0, 1}, {1, 2, 3}, {4}] + >>> foo(G, partition) + Traceback (most recent call last): + ... + networkx.exception.NetworkXError: `partition` is not a valid partition of the nodes of G + + """ + if is_partition(G, partition): + return G, partition + raise nx.NetworkXError("`partition` is not a valid partition of the nodes of G") + + +require_partition = argmap(_require_partition, (0, 1)) + + +@nx._dispatchable +def intra_community_edges(G, partition): + """Returns the number of intra-community edges for a partition of `G`. + + Parameters + ---------- + G : NetworkX graph. + + partition : iterable of sets of nodes + This must be a partition of the nodes of `G`. + + The "intra-community edges" are those edges joining a pair of nodes + in the same block of the partition. + + """ + return sum(G.subgraph(block).size() for block in partition) + + +@nx._dispatchable +def inter_community_edges(G, partition): + """Returns the number of inter-community edges for a partition of `G`. + according to the given + partition of the nodes of `G`. + + Parameters + ---------- + G : NetworkX graph. + + partition : iterable of sets of nodes + This must be a partition of the nodes of `G`. + + The *inter-community edges* are those edges joining a pair of nodes + in different blocks of the partition. + + Implementation note: this function creates an intermediate graph + that may require the same amount of memory as that of `G`. + + """ + # Alternate implementation that does not require constructing a new + # graph object (but does require constructing an affiliation + # dictionary): + # + # aff = dict(chain.from_iterable(((v, block) for v in block) + # for block in partition)) + # return sum(1 for u, v in G.edges() if aff[u] != aff[v]) + # + MG = nx.MultiDiGraph if G.is_directed() else nx.MultiGraph + return nx.quotient_graph(G, partition, create_using=MG).size() + + +@nx._dispatchable +def inter_community_non_edges(G, partition): + """Returns the number of inter-community non-edges according to the + given partition of the nodes of `G`. + + Parameters + ---------- + G : NetworkX graph. + + partition : iterable of sets of nodes + This must be a partition of the nodes of `G`. + + A *non-edge* is a pair of nodes (undirected if `G` is undirected) + that are not adjacent in `G`. The *inter-community non-edges* are + those non-edges on a pair of nodes in different blocks of the + partition. + + Implementation note: this function creates two intermediate graphs, + which may require up to twice the amount of memory as required to + store `G`. + + """ + # Alternate implementation that does not require constructing two + # new graph objects (but does require constructing an affiliation + # dictionary): + # + # aff = dict(chain.from_iterable(((v, block) for v in block) + # for block in partition)) + # return sum(1 for u, v in nx.non_edges(G) if aff[u] != aff[v]) + # + return inter_community_edges(nx.complement(G), partition) + + +@nx._dispatchable(edge_attrs="weight") +def modularity(G, communities, weight="weight", resolution=1): + r"""Returns the modularity of the given partition of the graph. + + Modularity is defined in [1]_ as + + .. math:: + Q = \frac{1}{2m} \sum_{ij} \left( A_{ij} - \gamma\frac{k_ik_j}{2m}\right) + \delta(c_i,c_j) + + where $m$ is the number of edges (or sum of all edge weights as in [5]_), + $A$ is the adjacency matrix of `G`, $k_i$ is the (weighted) degree of $i$, + $\gamma$ is the resolution parameter, and $\delta(c_i, c_j)$ is 1 if $i$ and + $j$ are in the same community else 0. + + According to [2]_ (and verified by some algebra) this can be reduced to + + .. math:: + Q = \sum_{c=1}^{n} + \left[ \frac{L_c}{m} - \gamma\left( \frac{k_c}{2m} \right) ^2 \right] + + where the sum iterates over all communities $c$, $m$ is the number of edges, + $L_c$ is the number of intra-community links for community $c$, + $k_c$ is the sum of degrees of the nodes in community $c$, + and $\gamma$ is the resolution parameter. + + The resolution parameter sets an arbitrary tradeoff between intra-group + edges and inter-group edges. More complex grouping patterns can be + discovered by analyzing the same network with multiple values of gamma + and then combining the results [3]_. That said, it is very common to + simply use gamma=1. More on the choice of gamma is in [4]_. + + The second formula is the one actually used in calculation of the modularity. + For directed graphs the second formula replaces $k_c$ with $k^{in}_c k^{out}_c$. + + Parameters + ---------- + G : NetworkX Graph + + communities : list or iterable of set of nodes + These node sets must represent a partition of G's nodes. + + weight : string or None, optional (default="weight") + The edge attribute that holds the numerical value used + as a weight. If None or an edge does not have that attribute, + then that edge has weight 1. + + resolution : float (default=1) + If resolution is less than 1, modularity favors larger communities. + Greater than 1 favors smaller communities. + + Returns + ------- + Q : float + The modularity of the partition. + + Raises + ------ + NotAPartition + If `communities` is not a partition of the nodes of `G`. + + Examples + -------- + >>> G = nx.barbell_graph(3, 0) + >>> nx.community.modularity(G, [{0, 1, 2}, {3, 4, 5}]) + 0.35714285714285715 + >>> nx.community.modularity(G, nx.community.label_propagation_communities(G)) + 0.35714285714285715 + + References + ---------- + .. [1] M. E. J. Newman "Networks: An Introduction", page 224. + Oxford University Press, 2011. + .. [2] Clauset, Aaron, Mark EJ Newman, and Cristopher Moore. + "Finding community structure in very large networks." + Phys. Rev. E 70.6 (2004). + .. [3] Reichardt and Bornholdt "Statistical Mechanics of Community Detection" + Phys. Rev. E 74, 016110, 2006. https://doi.org/10.1103/PhysRevE.74.016110 + .. [4] M. E. J. Newman, "Equivalence between modularity optimization and + maximum likelihood methods for community detection" + Phys. Rev. E 94, 052315, 2016. https://doi.org/10.1103/PhysRevE.94.052315 + .. [5] Blondel, V.D. et al. "Fast unfolding of communities in large + networks" J. Stat. Mech 10008, 1-12 (2008). + https://doi.org/10.1088/1742-5468/2008/10/P10008 + """ + if not isinstance(communities, list): + communities = list(communities) + if not is_partition(G, communities): + raise NotAPartition(G, communities) + + directed = G.is_directed() + if directed: + out_degree = dict(G.out_degree(weight=weight)) + in_degree = dict(G.in_degree(weight=weight)) + m = sum(out_degree.values()) + norm = 1 / m**2 + else: + out_degree = in_degree = dict(G.degree(weight=weight)) + deg_sum = sum(out_degree.values()) + m = deg_sum / 2 + norm = 1 / deg_sum**2 + + def community_contribution(community): + comm = set(community) + L_c = sum(wt for u, v, wt in G.edges(comm, data=weight, default=1) if v in comm) + + out_degree_sum = sum(out_degree[u] for u in comm) + in_degree_sum = sum(in_degree[u] for u in comm) if directed else out_degree_sum + + return L_c / m - resolution * out_degree_sum * in_degree_sum * norm + + return sum(map(community_contribution, communities)) + + +@require_partition +@nx._dispatchable +def partition_quality(G, partition): + """Returns the coverage and performance of a partition of G. + + The *coverage* of a partition is the ratio of the number of + intra-community edges to the total number of edges in the graph. + + The *performance* of a partition is the number of + intra-community edges plus inter-community non-edges divided by the total + number of potential edges. + + This algorithm has complexity $O(C^2 + L)$ where C is the number of + communities and L is the number of links. + + Parameters + ---------- + G : NetworkX graph + + partition : sequence + Partition of the nodes of `G`, represented as a sequence of + sets of nodes (blocks). Each block of the partition represents a + community. + + Returns + ------- + (float, float) + The (coverage, performance) tuple of the partition, as defined above. + + Raises + ------ + NetworkXError + If `partition` is not a valid partition of the nodes of `G`. + + Notes + ----- + If `G` is a multigraph; + - for coverage, the multiplicity of edges is counted + - for performance, the result is -1 (total number of possible edges is not defined) + + References + ---------- + .. [1] Santo Fortunato. + "Community Detection in Graphs". + *Physical Reports*, Volume 486, Issue 3--5 pp. 75--174 + + """ + + node_community = {} + for i, community in enumerate(partition): + for node in community: + node_community[node] = i + + # `performance` is not defined for multigraphs + if not G.is_multigraph(): + # Iterate over the communities, quadratic, to calculate `possible_inter_community_edges` + possible_inter_community_edges = sum( + len(p1) * len(p2) for p1, p2 in combinations(partition, 2) + ) + + if G.is_directed(): + possible_inter_community_edges *= 2 + else: + possible_inter_community_edges = 0 + + # Compute the number of edges in the complete graph -- `n` nodes, + # directed or undirected, depending on `G` + n = len(G) + total_pairs = n * (n - 1) + if not G.is_directed(): + total_pairs //= 2 + + intra_community_edges = 0 + inter_community_non_edges = possible_inter_community_edges + + # Iterate over the links to count `intra_community_edges` and `inter_community_non_edges` + for e in G.edges(): + if node_community[e[0]] == node_community[e[1]]: + intra_community_edges += 1 + else: + inter_community_non_edges -= 1 + + coverage = intra_community_edges / len(G.edges) + + if G.is_multigraph(): + performance = -1.0 + else: + performance = (intra_community_edges + inter_community_non_edges) / total_pairs + + return coverage, performance diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ca1b88a7297996bdf4a476161664272436e0eea Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_asyn_fluid.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_asyn_fluid.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d86082a9da7957a8eca6f11cdd5a989aec71528 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_asyn_fluid.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_bipartitions.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_bipartitions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d567c97788102d7bbb0f25782ce801c4d4d2cf7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_bipartitions.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_centrality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_centrality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c0c5aef06763bf40274d9c7609eb8490a66910f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_centrality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_divisive.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_divisive.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0ed3496aea94d861d1070c24750a677d0ac2e47 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_divisive.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_kclique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_kclique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61182909db935f9496edcd1f39486271e849deb2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_kclique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_label_propagation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_label_propagation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e9c3694b9f44c641998bd8a9437d5eed9cd01e4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_label_propagation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_leiden.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_leiden.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41518d6f0a7229b8bd341c342f069fffa8aef6cc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_leiden.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_local.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_local.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f816c798d023ed18f47fadbeaf83000fb4d1e8f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_local.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_louvain.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_louvain.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..914e4c0fae5cd92e164c70a28461a7ff5a8ddfa3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_louvain.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_lukes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_lukes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df3fb1f4cd9f011de193ead594399024bc968c94 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_lukes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_modularity_max.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_modularity_max.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..001ce097a86d212f3a3ea6c232ec1ad6c2f849ad Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_modularity_max.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_quality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_quality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..691fd9b6ce963480e306ef9292a48e2960c714fa Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_quality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..098771fa9d9e41ea8955b47712d79633b1ed0739 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/__pycache__/test_utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py new file mode 100644 index 0000000000000000000000000000000000000000..a5463034ad48725880639cd6a02329e80c51d977 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_asyn_fluid.py @@ -0,0 +1,147 @@ +import pytest + +import networkx as nx +from networkx import Graph, NetworkXError +from networkx.algorithms.community import asyn_fluidc + + +@pytest.mark.parametrize("graph_constructor", (nx.DiGraph, nx.MultiGraph)) +def test_raises_on_directed_and_multigraphs(graph_constructor): + G = graph_constructor([(0, 1), (1, 2)]) + with pytest.raises(nx.NetworkXNotImplemented): + nx.community.asyn_fluidc(G, 1) + + +def test_exceptions(): + test = Graph() + test.add_node("a") + pytest.raises(NetworkXError, asyn_fluidc, test, "hi") + pytest.raises(NetworkXError, asyn_fluidc, test, -1) + pytest.raises(NetworkXError, asyn_fluidc, test, 3) + with pytest.raises(ValueError, match="must be greater than 0"): + asyn_fluidc(test, 1, max_iter=0) + test.add_node("b") + pytest.raises(NetworkXError, asyn_fluidc, test, 1) + + +def test_single_node(): + test = Graph() + + test.add_node("a") + + # ground truth + ground_truth = {frozenset(["a"])} + + communities = asyn_fluidc(test, 1) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_two_nodes(): + test = Graph() + + test.add_edge("a", "b") + + # ground truth + ground_truth = {frozenset(["a"]), frozenset(["b"])} + + communities = asyn_fluidc(test, 2) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_two_clique_communities(): + test = Graph() + + # c1 + test.add_edge("a", "b") + test.add_edge("a", "c") + test.add_edge("b", "c") + + # connection + test.add_edge("c", "d") + + # c2 + test.add_edge("d", "e") + test.add_edge("d", "f") + test.add_edge("f", "e") + + # ground truth + ground_truth = {frozenset(["a", "c", "b"]), frozenset(["e", "d", "f"])} + + communities = asyn_fluidc(test, 2, seed=7) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_five_clique_ring(): + test = Graph() + + # c1 + test.add_edge("1a", "1b") + test.add_edge("1a", "1c") + test.add_edge("1a", "1d") + test.add_edge("1b", "1c") + test.add_edge("1b", "1d") + test.add_edge("1c", "1d") + + # c2 + test.add_edge("2a", "2b") + test.add_edge("2a", "2c") + test.add_edge("2a", "2d") + test.add_edge("2b", "2c") + test.add_edge("2b", "2d") + test.add_edge("2c", "2d") + + # c3 + test.add_edge("3a", "3b") + test.add_edge("3a", "3c") + test.add_edge("3a", "3d") + test.add_edge("3b", "3c") + test.add_edge("3b", "3d") + test.add_edge("3c", "3d") + + # c4 + test.add_edge("4a", "4b") + test.add_edge("4a", "4c") + test.add_edge("4a", "4d") + test.add_edge("4b", "4c") + test.add_edge("4b", "4d") + test.add_edge("4c", "4d") + + # c5 + test.add_edge("5a", "5b") + test.add_edge("5a", "5c") + test.add_edge("5a", "5d") + test.add_edge("5b", "5c") + test.add_edge("5b", "5d") + test.add_edge("5c", "5d") + + # connections + test.add_edge("1a", "2c") + test.add_edge("2a", "3c") + test.add_edge("3a", "4c") + test.add_edge("4a", "5c") + test.add_edge("5a", "1c") + + # ground truth + ground_truth = { + frozenset(["1a", "1b", "1c", "1d"]), + frozenset(["2a", "2b", "2c", "2d"]), + frozenset(["3a", "3b", "3c", "3d"]), + frozenset(["4a", "4b", "4c", "4d"]), + frozenset(["5a", "5b", "5c", "5d"]), + } + + communities = asyn_fluidc(test, 5, seed=9) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_asyn_fluidc_max_iter(): + """Check that setting `max_iter` stops the algorithm early.""" + test = nx.barbell_graph(3, 2) + + c1 = asyn_fluidc(test, 2, max_iter=1, seed=42) + c2 = asyn_fluidc(test, 2, max_iter=100, seed=42) + assert {map(frozenset, c1)} != {map(frozenset, c2)} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_bipartitions.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_bipartitions.py new file mode 100644 index 0000000000000000000000000000000000000000..c3e6a51d65391b6c9f1bafd3f064d7f50da73428 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_bipartitions.py @@ -0,0 +1,157 @@ +from itertools import permutations + +import pytest + +import networkx as nx +from networkx.algorithms.community import ( + greedy_node_swap_bipartition, + kernighan_lin_bisection, +) + + +def assert_partition_equal(x, y): + assert set(map(frozenset, x)) == set(map(frozenset, y)) + + +def test_partition(): + G = nx.barbell_graph(3, 0) + split = kernighan_lin_bisection(G) + assert_partition_equal(split, [{0, 1, 2}, {3, 4, 5}]) + + +def test_partition_argument(): + G = nx.barbell_graph(3, 0) + partition = [{0, 1, 2}, {3, 4, 5}] + split = kernighan_lin_bisection(G, partition) + assert_partition_equal(split, partition) + + +def test_partition_argument_non_integer_nodes(): + G = nx.Graph([("A", "B"), ("A", "C"), ("B", "C"), ("C", "D")]) + partition = ({"A", "B"}, {"C", "D"}) + split = kernighan_lin_bisection(G, partition) + assert_partition_equal(split, partition) + + +def test_seed_argument(): + G = nx.barbell_graph(3, 0) + split = kernighan_lin_bisection(G, seed=1) + assert_partition_equal(split, [{0, 1, 2}, {3, 4, 5}]) + + +def test_non_disjoint_partition(): + with pytest.raises(nx.NetworkXError): + G = nx.barbell_graph(3, 0) + partition = ({0, 1, 2}, {2, 3, 4, 5}) + kernighan_lin_bisection(G, partition) + + +def test_kernighan_lin_too_many_blocks(): + with pytest.raises(nx.NetworkXError): + G = nx.barbell_graph(3, 0) + partition = ({0, 1}, {2}, {3, 4, 5}) + kernighan_lin_bisection(G, partition) + + +def test_multigraph(): + G = nx.cycle_graph(4) + M = nx.MultiGraph(G.edges()) + M.add_edges_from(G.edges()) + M.remove_edge(1, 2) + for labels in permutations(range(4)): + mapping = dict(zip(M, labels)) + A, B = kernighan_lin_bisection(nx.relabel_nodes(M, mapping), seed=0) + assert_partition_equal( + [A, B], [{mapping[0], mapping[1]}, {mapping[2], mapping[3]}] + ) + + +def test_max_iter_argument(): + G = nx.Graph( + [ + ("A", "B", {"weight": 1}), + ("A", "C", {"weight": 2}), + ("A", "D", {"weight": 3}), + ("A", "E", {"weight": 2}), + ("A", "F", {"weight": 4}), + ("B", "C", {"weight": 1}), + ("B", "D", {"weight": 4}), + ("B", "E", {"weight": 2}), + ("B", "F", {"weight": 1}), + ("C", "D", {"weight": 3}), + ("C", "E", {"weight": 2}), + ("C", "F", {"weight": 1}), + ("D", "E", {"weight": 4}), + ("D", "F", {"weight": 3}), + ("E", "F", {"weight": 2}), + ] + ) + partition = ({"A", "B", "C"}, {"D", "E", "F"}) + split = kernighan_lin_bisection(G, partition, max_iter=1) + assert_partition_equal(split, ({"A", "F", "C"}, {"D", "E", "B"})) + + +def test_weight_function(): + G = nx.cycle_graph(4) + + def my_weight(u, v, d): + if u == 2 and v == 3: + return None + return u + v + + split = kernighan_lin_bisection(G, weight=my_weight) + assert_partition_equal(split, ({1, 2}, {0, 3})) + + +## Test Spectral Modularity Bipartition + + +def test_spectral_bipartition(): + pytest.importorskip("scipy") + G = nx.barbell_graph(3, 0) + split = nx.community.spectral_modularity_bipartition(G) + soln = ({3, 4, 5}, {0, 1, 2}) + assert set(map(frozenset, split)) == set(map(frozenset, soln)) + + +def test_karate_club(): + pytest.importorskip("scipy") + G = nx.karate_club_graph() + MrHi = {v for v, club in G.nodes.data("club") if club == "Mr. Hi"} + Officer = {v for v, club in G.nodes.data("club") if club == "Officer"} + split = nx.community.spectral_modularity_bipartition(G) + + # spectral method misplaces member 8 + MrHi.remove(8) + Officer.add(8) + soln = (MrHi, Officer) + assert set(map(frozenset, split)) == set(map(frozenset, soln)) + + +## Test Node Swap Greedy Bipartition + + +def test_greedy_bipartition(): + G = nx.barbell_graph(3, 0) + split = nx.community.greedy_node_swap_bipartition(G) + soln = ({0, 1, 2}, {3, 4, 5}) + assert set(map(frozenset, split)) == set(map(frozenset, soln)) + + +def test_greedy_non_disjoint_partition(): + G = nx.barbell_graph(3, 0) + split = ({0, 1, 2}, {2, 3, 4, 5}) + with pytest.raises(nx.NetworkXError): + nx.community.greedy_node_swap_bipartition(G, init_split=split) + + +def test_greedy_node_swap_too_many_blocks(): + G = nx.barbell_graph(3, 0) + split = ({0, 1}, {2}, {3, 4, 5}) + with pytest.raises(nx.NetworkXError): + nx.community.greedy_node_swap_bipartition(G, init_split=split) + + +def test_greedy_multigraph_disallowed(): + with pytest.raises(nx.NetworkXNotImplemented): + nx.community.greedy_node_swap_bipartition(nx.MultiGraph()) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py new file mode 100644 index 0000000000000000000000000000000000000000..1a8982f0d7fa16f6c7114a0bafe85bf205988c93 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_centrality.py @@ -0,0 +1,85 @@ +"""Unit tests for the :mod:`networkx.algorithms.community.centrality` +module. + +""" + +from operator import itemgetter + +import networkx as nx + + +def set_of_sets(iterable): + return set(map(frozenset, iterable)) + + +def validate_communities(result, expected): + assert set_of_sets(result) == set_of_sets(expected) + + +def validate_possible_communities(result, *expected): + assert any(set_of_sets(result) == set_of_sets(p) for p in expected) + + +class TestGirvanNewman: + """Unit tests for the + :func:`networkx.algorithms.community.centrality.girvan_newman` + function. + + """ + + def test_no_edges(self): + G = nx.empty_graph(3) + communities = list(nx.community.girvan_newman(G)) + assert len(communities) == 1 + validate_communities(communities[0], [{0}, {1}, {2}]) + + def test_undirected(self): + # Start with the graph .-.-.-. + G = nx.path_graph(4) + communities = list(nx.community.girvan_newman(G)) + assert len(communities) == 3 + # After one removal, we get the graph .-. .-. + validate_communities(communities[0], [{0, 1}, {2, 3}]) + # After the next, we get the graph .-. . ., but there are two + # symmetric possible versions. + validate_possible_communities( + communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}] + ) + # After the last removal, we always get the empty graph. + validate_communities(communities[2], [{0}, {1}, {2}, {3}]) + + def test_directed(self): + G = nx.DiGraph(nx.path_graph(4)) + communities = list(nx.community.girvan_newman(G)) + assert len(communities) == 3 + validate_communities(communities[0], [{0, 1}, {2, 3}]) + validate_possible_communities( + communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}] + ) + validate_communities(communities[2], [{0}, {1}, {2}, {3}]) + + def test_selfloops(self): + G = nx.path_graph(4) + G.add_edge(0, 0) + G.add_edge(2, 2) + communities = list(nx.community.girvan_newman(G)) + assert len(communities) == 3 + validate_communities(communities[0], [{0, 1}, {2, 3}]) + validate_possible_communities( + communities[1], [{0}, {1}, {2, 3}], [{0, 1}, {2}, {3}] + ) + validate_communities(communities[2], [{0}, {1}, {2}, {3}]) + + def test_most_valuable_edge(self): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, 3), (1, 2, 2), (2, 3, 1)]) + # Let the most valuable edge be the one with the highest weight. + + def heaviest(G): + return max(G.edges(data="weight"), key=itemgetter(2))[:2] + + communities = list(nx.community.girvan_newman(G, heaviest)) + assert len(communities) == 3 + validate_communities(communities[0], [{0}, {1, 2, 3}]) + validate_communities(communities[1], [{0}, {1}, {2, 3}]) + validate_communities(communities[2], [{0}, {1}, {2}, {3}]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py new file mode 100644 index 0000000000000000000000000000000000000000..6331503f97eaabee965a7f7f302b30e88601687e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_divisive.py @@ -0,0 +1,106 @@ +import pytest + +import networkx as nx + + +def test_edge_betweenness_partition(): + G = nx.barbell_graph(3, 0) + C = nx.community.edge_betweenness_partition(G, 2) + answer = [{0, 1, 2}, {3, 4, 5}] + assert len(C) == len(answer) + for s in answer: + assert s in C + + G = nx.barbell_graph(3, 1) + C = nx.community.edge_betweenness_partition(G, 3) + answer = [{0, 1, 2}, {4, 5, 6}, {3}] + assert len(C) == len(answer) + for s in answer: + assert s in C + + C = nx.community.edge_betweenness_partition(G, 7) + answer = [{n} for n in G] + assert len(C) == len(answer) + for s in answer: + assert s in C + + C = nx.community.edge_betweenness_partition(G, 1) + assert C == [set(G)] + + C = nx.community.edge_betweenness_partition(G, 1, weight="weight") + assert C == [set(G)] + + with pytest.raises(nx.NetworkXError): + nx.community.edge_betweenness_partition(G, 0) + + with pytest.raises(nx.NetworkXError): + nx.community.edge_betweenness_partition(G, -1) + + with pytest.raises(nx.NetworkXError): + nx.community.edge_betweenness_partition(G, 10) + + +def test_edge_current_flow_betweenness_partition(): + pytest.importorskip("scipy") + + G = nx.barbell_graph(3, 0) + C = nx.community.edge_current_flow_betweenness_partition(G, 2) + answer = [{0, 1, 2}, {3, 4, 5}] + assert len(C) == len(answer) + for s in answer: + assert s in C + + G = nx.barbell_graph(3, 1) + C = nx.community.edge_current_flow_betweenness_partition(G, 2) + answers = [[{0, 1, 2, 3}, {4, 5, 6}], [{0, 1, 2}, {3, 4, 5, 6}]] + assert len(C) == len(answers[0]) + assert any(all(s in answer for s in C) for answer in answers) + + C = nx.community.edge_current_flow_betweenness_partition(G, 3) + answer = [{0, 1, 2}, {4, 5, 6}, {3}] + assert len(C) == len(answer) + for s in answer: + assert s in C + + C = nx.community.edge_current_flow_betweenness_partition(G, 4) + answers = [[{1, 2}, {4, 5, 6}, {3}, {0}], [{0, 1, 2}, {5, 6}, {3}, {4}]] + assert len(C) == len(answers[0]) + assert any(all(s in answer for s in C) for answer in answers) + + C = nx.community.edge_current_flow_betweenness_partition(G, 5) + answer = [{1, 2}, {5, 6}, {3}, {0}, {4}] + assert len(C) == len(answer) + for s in answer: + assert s in C + + C = nx.community.edge_current_flow_betweenness_partition(G, 6) + answers = [[{2}, {5, 6}, {3}, {0}, {4}, {1}], [{1, 2}, {6}, {3}, {0}, {4}, {5}]] + assert len(C) == len(answers[0]) + assert any(all(s in answer for s in C) for answer in answers) + + C = nx.community.edge_current_flow_betweenness_partition(G, 7) + answer = [{n} for n in G] + assert len(C) == len(answer) + for s in answer: + assert s in C + + C = nx.community.edge_current_flow_betweenness_partition(G, 1) + assert C == [set(G)] + + C = nx.community.edge_current_flow_betweenness_partition(G, 1, weight="weight") + assert C == [set(G)] + + with pytest.raises(nx.NetworkXError): + nx.community.edge_current_flow_betweenness_partition(G, 0) + + with pytest.raises(nx.NetworkXError): + nx.community.edge_current_flow_betweenness_partition(G, -1) + + with pytest.raises(nx.NetworkXError): + nx.community.edge_current_flow_betweenness_partition(G, 10) + + N = 10 + G = nx.empty_graph(N) + for i in range(2, N - 1): + C = nx.community.edge_current_flow_betweenness_partition(G, i) + assert C == [{n} for n in G] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py new file mode 100644 index 0000000000000000000000000000000000000000..aa0b7e823e2780f734180562fa3fb8ce5a671312 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_kclique.py @@ -0,0 +1,91 @@ +from itertools import combinations + +import pytest + +import networkx as nx + + +def test_overlapping_K5(): + G = nx.Graph() + G.add_edges_from(combinations(range(5), 2)) # Add a five clique + G.add_edges_from(combinations(range(2, 7), 2)) # Add another five clique + c = list(nx.community.k_clique_communities(G, 4)) + assert c == [frozenset(range(7))] + c = set(nx.community.k_clique_communities(G, 5)) + assert c == {frozenset(range(5)), frozenset(range(2, 7))} + + +def test_isolated_K5(): + G = nx.Graph() + G.add_edges_from(combinations(range(5), 2)) # Add a five clique + G.add_edges_from(combinations(range(5, 10), 2)) # Add another five clique + c = set(nx.community.k_clique_communities(G, 5)) + assert c == {frozenset(range(5)), frozenset(range(5, 10))} + + +class TestZacharyKarateClub: + def setup_method(self): + self.G = nx.karate_club_graph() + + def _check_communities(self, k, expected): + communities = set(nx.community.k_clique_communities(self.G, k)) + assert communities == expected + + def test_k2(self): + # clique percolation with k=2 is just connected components + expected = {frozenset(self.G)} + self._check_communities(2, expected) + + def test_k3(self): + comm1 = [ + 0, + 1, + 2, + 3, + 7, + 8, + 12, + 13, + 14, + 15, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + ] + comm2 = [0, 4, 5, 6, 10, 16] + comm3 = [24, 25, 31] + expected = {frozenset(comm1), frozenset(comm2), frozenset(comm3)} + self._check_communities(3, expected) + + def test_k4(self): + expected = { + frozenset([0, 1, 2, 3, 7, 13]), + frozenset([8, 32, 30, 33]), + frozenset([32, 33, 29, 23]), + } + self._check_communities(4, expected) + + def test_k5(self): + expected = {frozenset([0, 1, 2, 3, 7, 13])} + self._check_communities(5, expected) + + def test_k6(self): + expected = set() + self._check_communities(6, expected) + + +def test_bad_k(): + with pytest.raises(nx.NetworkXError): + list(nx.community.k_clique_communities(nx.Graph(), 1)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py new file mode 100644 index 0000000000000000000000000000000000000000..4be72dbf27281c58d973e1d0d84d101fa369d43d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_label_propagation.py @@ -0,0 +1,241 @@ +from itertools import chain, combinations + +import pytest + +import networkx as nx + + +def test_directed_not_supported(): + with pytest.raises(nx.NetworkXNotImplemented): + # not supported for directed graphs + test = nx.DiGraph() + test.add_edge("a", "b") + test.add_edge("a", "c") + test.add_edge("b", "d") + result = nx.community.label_propagation_communities(test) + + +def test_iterator_vs_iterable(): + G = nx.empty_graph("a") + assert list(nx.community.label_propagation_communities(G)) == [{"a"}] + for community in nx.community.label_propagation_communities(G): + assert community == {"a"} + pytest.raises(TypeError, next, nx.community.label_propagation_communities(G)) + + +def test_one_node(): + test = nx.Graph() + test.add_node("a") + + # The expected communities are: + ground_truth = {frozenset(["a"])} + + communities = nx.community.label_propagation_communities(test) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_unconnected_communities(): + test = nx.Graph() + # community 1 + test.add_edge("a", "c") + test.add_edge("a", "d") + test.add_edge("d", "c") + # community 2 + test.add_edge("b", "e") + test.add_edge("e", "f") + test.add_edge("f", "b") + + # The expected communities are: + ground_truth = {frozenset(["a", "c", "d"]), frozenset(["b", "e", "f"])} + + communities = nx.community.label_propagation_communities(test) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + +def test_connected_communities(): + test = nx.Graph() + # community 1 + test.add_edge("a", "b") + test.add_edge("c", "a") + test.add_edge("c", "b") + test.add_edge("d", "a") + test.add_edge("d", "b") + test.add_edge("d", "c") + test.add_edge("e", "a") + test.add_edge("e", "b") + test.add_edge("e", "c") + test.add_edge("e", "d") + # community 2 + test.add_edge("1", "2") + test.add_edge("3", "1") + test.add_edge("3", "2") + test.add_edge("4", "1") + test.add_edge("4", "2") + test.add_edge("4", "3") + test.add_edge("5", "1") + test.add_edge("5", "2") + test.add_edge("5", "3") + test.add_edge("5", "4") + # edge between community 1 and 2 + test.add_edge("a", "1") + # community 3 + test.add_edge("x", "y") + # community 4 with only a single node + test.add_node("z") + + # The expected communities are: + ground_truth1 = { + frozenset(["a", "b", "c", "d", "e"]), + frozenset(["1", "2", "3", "4", "5"]), + frozenset(["x", "y"]), + frozenset(["z"]), + } + ground_truth2 = { + frozenset(["a", "b", "c", "d", "e", "1", "2", "3", "4", "5"]), + frozenset(["x", "y"]), + frozenset(["z"]), + } + ground_truth = (ground_truth1, ground_truth2) + + communities = nx.community.label_propagation_communities(test) + result = {frozenset(c) for c in communities} + assert result in ground_truth + + +def test_termination(): + # ensure termination of asyn_lpa_communities in two cases + # that led to an endless loop in a previous version + test1 = nx.karate_club_graph() + test2 = nx.caveman_graph(2, 10) + test2.add_edges_from([(0, 20), (20, 10)]) + nx.community.asyn_lpa_communities(test1) + nx.community.asyn_lpa_communities(test2) + + +class TestAsynLpaCommunities: + def _check_communities(self, G, expected): + """Checks that the communities computed from the given graph ``G`` + using the :func:`~networkx.asyn_lpa_communities` function match + the set of nodes given in ``expected``. + + ``expected`` must be a :class:`set` of :class:`frozenset` + instances, each element of which is a node in the graph. + + """ + communities = nx.community.asyn_lpa_communities(G) + result = {frozenset(c) for c in communities} + assert result == expected + + def test_null_graph(self): + G = nx.null_graph() + ground_truth = set() + self._check_communities(G, ground_truth) + + def test_single_node(self): + G = nx.empty_graph(1) + ground_truth = {frozenset([0])} + self._check_communities(G, ground_truth) + + def test_simple_communities(self): + # This graph is the disjoint union of two triangles. + G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"]) + ground_truth = {frozenset("abc"), frozenset("def")} + self._check_communities(G, ground_truth) + + def test_seed_argument(self): + G = nx.Graph(["ab", "ac", "bc", "de", "df", "fe"]) + ground_truth = {frozenset("abc"), frozenset("def")} + communities = nx.community.asyn_lpa_communities(G, seed=1) + result = {frozenset(c) for c in communities} + assert result == ground_truth + + def test_several_communities(self): + # This graph is the disjoint union of five triangles. + ground_truth = {frozenset(range(3 * i, 3 * (i + 1))) for i in range(5)} + edges = chain.from_iterable(combinations(c, 2) for c in ground_truth) + G = nx.Graph(edges) + self._check_communities(G, ground_truth) + + +class TestFastLabelPropagationCommunities: + N = 100 # number of nodes + K = 15 # average node degree + + def _check_communities(self, G, truth, weight=None, seed=42): + C = nx.community.fast_label_propagation_communities(G, weight=weight, seed=seed) + assert {frozenset(c) for c in C} == truth + + def test_null_graph(self): + G = nx.null_graph() + truth = set() + self._check_communities(G, truth) + + def test_empty_graph(self): + G = nx.empty_graph(self.N) + truth = {frozenset([i]) for i in G} + self._check_communities(G, truth) + + def test_star_graph(self): + G = nx.star_graph(self.N) + truth = {frozenset(G)} + self._check_communities(G, truth) + + def test_complete_graph(self): + G = nx.complete_graph(self.N) + truth = {frozenset(G)} + self._check_communities(G, truth) + + def test_bipartite_graph(self): + G = nx.complete_bipartite_graph(self.N // 2, self.N // 2) + truth = {frozenset(G)} + self._check_communities(G, truth) + + def test_random_graph(self): + G = nx.gnm_random_graph(self.N, self.N * self.K // 2, seed=42) + truth = {frozenset(G)} + self._check_communities(G, truth) + + def test_disjoin_cliques(self): + G = nx.Graph(["ab", "AB", "AC", "BC", "12", "13", "14", "23", "24", "34"]) + truth = {frozenset("ab"), frozenset("ABC"), frozenset("1234")} + self._check_communities(G, truth) + + def test_ring_of_cliques(self): + N, K = self.N, self.K + G = nx.ring_of_cliques(N, K) + truth = {frozenset([K * i + k for k in range(K)]) for i in range(N)} + self._check_communities(G, truth) + + def test_larger_graph(self): + G = nx.gnm_random_graph(100 * self.N, 50 * self.N * self.K, seed=42) + nx.community.fast_label_propagation_communities(G) + + def test_graph_type(self): + G1 = nx.complete_graph(self.N, nx.MultiDiGraph()) + G2 = nx.MultiGraph(G1) + G3 = nx.DiGraph(G1) + G4 = nx.Graph(G1) + truth = {frozenset(G1)} + self._check_communities(G1, truth) + self._check_communities(G2, truth) + self._check_communities(G3, truth) + self._check_communities(G4, truth) + + def test_weight_argument(self): + G = nx.MultiDiGraph() + G.add_edge(1, 2, weight=1.41) + G.add_edge(2, 1, weight=1.41) + G.add_edge(2, 3) + G.add_edge(3, 4, weight=3.14) + truth = {frozenset({1, 2}), frozenset({3, 4})} + self._check_communities(G, truth, weight="weight") + + def test_seed_argument(self): + G = nx.karate_club_graph() + C = nx.community.fast_label_propagation_communities(G, seed=2023) + truth = {frozenset(c) for c in C} + self._check_communities(G, truth, seed=2023) + # smoke test that seed=None works + C = nx.community.fast_label_propagation_communities(G, seed=None) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_leiden.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_leiden.py new file mode 100644 index 0000000000000000000000000000000000000000..7e61b94c7767cc5f218a1bd61477c657d58b802e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_leiden.py @@ -0,0 +1,138 @@ +import pytest + +import networkx as nx +from networkx.algorithms.community import leiden_communities, leiden_partitions + +# Leiden is not yet implemented by networkx, so only run tests in this file for +# backends that implement Leiden. +no_backends_for_leiden_communities = ( + "not set(nx.config.backend_priority.algos) & leiden_communities.backends" +) + +no_backends_for_leiden_partitions = ( + "not set(nx.config.backend_priority.algos) & leiden_partitions.backends" +) + + +def test_leiden_with_nx_backend(): + G = nx.karate_club_graph() + with pytest.raises(NotImplementedError): + nx.community.leiden_partitions(G, backend="networkx") + with pytest.raises(NotImplementedError): + nx.community.leiden_communities(G, backend="networkx") + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_modularity_increase(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + partition = [{u} for u in G.nodes()] + mod = nx.community.modularity(G, partition) + partition = nx.community.leiden_communities(G) + + assert nx.community.modularity(G, partition) > mod + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_valid_partition(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + partition = nx.community.leiden_communities(G) + + assert nx.community.is_partition(G, partition) + + +@pytest.mark.skipif(no_backends_for_leiden_partitions) +def test_partition_iterator(): + G = nx.path_graph(15) + parts_iter = nx.community.leiden_partitions(G, seed=42) + first_part = next(parts_iter) + first_copy = [s.copy() for s in first_part] + + # check 1st part stays fixed even after 2nd iteration (like gh-5901 in louvain) + assert first_copy[0] == first_part[0] + second_part = next(parts_iter) + assert first_copy[0] == first_part[0] + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_none_weight_param(): + G = nx.karate_club_graph() + nx.set_edge_attributes( + G, {edge: i * i for i, edge in enumerate(G.edges)}, name="foo" + ) + + partition1 = nx.community.leiden_communities(G, weight=None, seed=2) + partition2 = nx.community.leiden_communities(G, weight="foo", seed=2) + partition3 = nx.community.leiden_communities(G, weight="weight", seed=2) + + assert partition1 != partition2 + assert partition2 != partition3 + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_quality(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + H = nx.MultiGraph(G) + + partition = nx.community.leiden_communities(G) + partition2 = nx.community.leiden_communities(H) + + quality = nx.community.partition_quality(G, partition)[0] + quality2 = nx.community.partition_quality(H, partition2)[0] + + assert quality >= 0.65 + assert quality2 >= 0.65 + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_resolution(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + + partition1 = nx.community.leiden_communities(G, resolution=0.5, seed=12) + partition2 = nx.community.leiden_communities(G, seed=12) + partition3 = nx.community.leiden_communities(G, resolution=2, seed=12) + + assert len(partition1) <= len(partition2) + assert len(partition2) <= len(partition3) + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_empty_graph(): + G = nx.Graph() + G.add_nodes_from(range(5)) + expected = [{0}, {1}, {2}, {3}, {4}] + assert nx.community.leiden_communities(G) == expected + + +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_directed_not_implemented(): + G = nx.cycle_graph(4, create_using=nx.DiGraph) + with pytest.raises(nx.NetworkXNotImplemented): + nx.community.leiden_communities(G) + + +@pytest.mark.skipif(no_backends_for_leiden_partitions) +@pytest.mark.skipif(no_backends_for_leiden_communities) +def test_max_level(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + parts_iter = nx.community.leiden_partitions(G, seed=42) + for max_level, expected in enumerate(parts_iter, 1): + partition = nx.community.leiden_communities(G, max_level=max_level, seed=42) + assert partition == expected + assert max_level > 1 # Ensure we are actually testing max_level + # max_level is an upper limit; it's okay if we stop before it's hit. + partition = nx.community.leiden_communities(G, max_level=max_level + 1, seed=42) + assert partition == expected + with pytest.raises( + ValueError, match="max_level argument must be a positive integer" + ): + nx.community.leiden_communities(G, max_level=0) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_local.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_local.py new file mode 100644 index 0000000000000000000000000000000000000000..d0ec43fb65b1674eef784db7833d223e59d6b5a6 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_local.py @@ -0,0 +1,76 @@ +import pytest + +import networkx as nx + + +def test_greedy_source_expansion_karate_club(): + G = nx.karate_club_graph() + + community = nx.community.greedy_source_expansion(G, source=16) + + expected = {0, 4, 5, 6, 10, 16} + + assert community == expected + + +def test_greedy_source_expansion_cutoff(): + G = nx.karate_club_graph() + + community = nx.community.greedy_source_expansion(G, source=16, cutoff=3) + + assert community == {5, 6, 16} + + +def test_greedy_source_expansion_invalid_method(): + G = nx.karate_club_graph() + + with pytest.raises(ValueError): + nx.community.greedy_source_expansion(G, source=16, cutoff=3, method="invalid") + + +def test_greedy_source_expansion_connected_component(): + G_edges = [(0, 2), (0, 1), (1, 0), (2, 1), (2, 0), (3, 4), (4, 3)] + G = nx.Graph(G_edges) + expected = {0, 1, 2} + community = nx.community.greedy_source_expansion(G, source=0) + assert community == expected + + +def test_greedy_source_expansion_directed_graph(): + G_edges = [ + (0, 2), + (0, 1), + (1, 0), + (2, 1), + (2, 0), + (3, 4), + (4, 3), + (4, 5), + (5, 3), + (5, 6), + (0, 6), + ] + G = nx.DiGraph(G_edges) + + expected = {0, 1, 2, 6} + community = nx.community.greedy_source_expansion(G, source=0) + assert community == expected + + +def test_greedy_source_expansion_multigraph(): + G = nx.MultiGraph(nx.karate_club_graph()) + G.add_edge(0, 1) + G.add_edge(0, 9) + + expected = {0, 4, 5, 6, 10, 16} + + community = nx.community.greedy_source_expansion(G, source=16) + + assert community == expected + + +def test_greedy_source_expansion_empty_graph(): + G = nx.Graph() + G.add_nodes_from(range(5)) + expected = {0} + assert nx.community.greedy_source_expansion(G, source=0) == expected diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py new file mode 100644 index 0000000000000000000000000000000000000000..816e6f143fe0837b5d8617a927f49e557ccd9668 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_louvain.py @@ -0,0 +1,264 @@ +import pytest + +import networkx as nx + + +def test_modularity_increase(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + partition = [{u} for u in G.nodes()] + mod = nx.community.modularity(G, partition) + partition = nx.community.louvain_communities(G) + + assert nx.community.modularity(G, partition) > mod + + +def test_valid_partition(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + H = G.to_directed() + partition = nx.community.louvain_communities(G) + partition2 = nx.community.louvain_communities(H) + + assert nx.community.is_partition(G, partition) + assert nx.community.is_partition(H, partition2) + + +def test_karate_club_partition(): + G = nx.karate_club_graph() + part = [ + {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21}, + {16, 4, 5, 6, 10}, + {23, 25, 27, 28, 24, 31}, + {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30}, + ] + partition = nx.community.louvain_communities(G, seed=2, weight=None) + + assert part == partition + + +def test_partition_iterator(): + G = nx.path_graph(15) + parts_iter = nx.community.louvain_partitions(G, seed=42) + first_part = next(parts_iter) + first_copy = [s.copy() for s in first_part] + + # gh-5901 reports sets changing after next partition is yielded + assert first_copy[0] == first_part[0] + second_part = next(parts_iter) + assert first_copy[0] == first_part[0] + + +def test_undirected_selfloops(): + G = nx.karate_club_graph() + expected_partition = nx.community.louvain_communities(G, seed=2, weight=None) + part = [ + {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21}, + {16, 4, 5, 6, 10}, + {23, 25, 27, 28, 24, 31}, + {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30}, + ] + assert expected_partition == part + + G.add_weighted_edges_from([(i, i, i * 1000) for i in range(9)]) + # large self-loop weight impacts partition + partition = nx.community.louvain_communities(G, seed=2, weight="weight") + assert part != partition + + # small self-loop weights aren't enough to impact partition in this graph + partition = nx.community.louvain_communities(G, seed=2, weight=None) + assert part == partition + + +def test_directed_selfloops(): + G = nx.DiGraph() + G.add_nodes_from(range(11)) + G_edges = [ + (0, 2), + (0, 1), + (1, 0), + (2, 1), + (2, 0), + (3, 4), + (4, 3), + (7, 8), + (8, 7), + (9, 10), + (10, 9), + ] + G.add_edges_from(G_edges) + G_expected_partition = nx.community.louvain_communities(G, seed=123, weight=None) + + G.add_weighted_edges_from([(i, i, i * 1000) for i in range(3)]) + # large self-loop weight impacts partition + G_partition = nx.community.louvain_communities(G, seed=123, weight="weight") + assert G_partition != G_expected_partition + + # small self-loop weights aren't enough to impact partition in this graph + G_partition = nx.community.louvain_communities(G, seed=123, weight=None) + assert G_partition == G_expected_partition + + +def test_directed_partition(): + """ + Test 2 cases that were looping infinitely + from issues #5175 and #5704 + """ + G = nx.DiGraph() + H = nx.DiGraph() + G.add_nodes_from(range(10)) + H.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + G_edges = [ + (0, 2), + (0, 1), + (1, 0), + (2, 1), + (2, 0), + (3, 4), + (4, 3), + (7, 8), + (8, 7), + (9, 10), + (10, 9), + ] + H_edges = [ + (1, 2), + (1, 6), + (1, 9), + (2, 3), + (2, 4), + (2, 5), + (3, 4), + (4, 3), + (4, 5), + (5, 4), + (6, 7), + (6, 8), + (9, 10), + (9, 11), + (10, 11), + (11, 10), + ] + G.add_edges_from(G_edges) + H.add_edges_from(H_edges) + + G_expected_partition = [{0, 1, 2}, {3, 4}, {5}, {6}, {8, 7}, {9, 10}] + G_partition = nx.community.louvain_communities(G, seed=123, weight=None) + + H_expected_partition = [{2, 3, 4, 5}, {8, 1, 6, 7}, {9, 10, 11}] + H_partition = nx.community.louvain_communities(H, seed=123, weight=None) + + assert G_partition == G_expected_partition + assert H_partition == H_expected_partition + + +def test_none_weight_param(): + G = nx.karate_club_graph() + nx.set_edge_attributes( + G, {edge: i * i for i, edge in enumerate(G.edges)}, name="foo" + ) + + part = [ + {0, 1, 2, 3, 7, 9, 11, 12, 13, 17, 19, 21}, + {16, 4, 5, 6, 10}, + {23, 25, 27, 28, 24, 31}, + {32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30}, + ] + partition1 = nx.community.louvain_communities(G, weight=None, seed=2) + partition2 = nx.community.louvain_communities(G, weight="foo", seed=2) + partition3 = nx.community.louvain_communities(G, weight="weight", seed=2) + + assert part == partition1 + assert part != partition2 + assert part != partition3 + assert partition2 != partition3 + + +def test_quality(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + H = nx.gn_graph(200, seed=1234) + I = nx.MultiGraph(G) + J = nx.MultiDiGraph(H) + + partition = nx.community.louvain_communities(G) + partition2 = nx.community.louvain_communities(H) + partition3 = nx.community.louvain_communities(I) + partition4 = nx.community.louvain_communities(J) + + quality = nx.community.partition_quality(G, partition)[0] + quality2 = nx.community.partition_quality(H, partition2)[0] + quality3 = nx.community.partition_quality(I, partition3)[0] + quality4 = nx.community.partition_quality(J, partition4)[0] + + assert quality >= 0.65 + assert quality2 >= 0.65 + assert quality3 >= 0.65 + assert quality4 >= 0.65 + + +def test_multigraph(): + G = nx.karate_club_graph() + H = nx.MultiGraph(G) + G.add_edge(0, 1, weight=10) + H.add_edge(0, 1, weight=9) + G.add_edge(0, 9, foo=20) + H.add_edge(0, 9, foo=20) + + partition1 = nx.community.louvain_communities(G, seed=1234) + partition2 = nx.community.louvain_communities(H, seed=1234) + partition3 = nx.community.louvain_communities(H, weight="foo", seed=1234) + + assert partition1 == partition2 != partition3 + + +def test_resolution(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + + partition1 = nx.community.louvain_communities(G, resolution=0.5, seed=12) + partition2 = nx.community.louvain_communities(G, seed=12) + partition3 = nx.community.louvain_communities(G, resolution=2, seed=12) + + assert len(partition1) <= len(partition2) <= len(partition3) + + +def test_threshold(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + partition1 = nx.community.louvain_communities(G, threshold=0.3, seed=2) + partition2 = nx.community.louvain_communities(G, seed=2) + mod1 = nx.community.modularity(G, partition1) + mod2 = nx.community.modularity(G, partition2) + + assert mod1 <= mod2 + + +def test_empty_graph(): + G = nx.Graph() + G.add_nodes_from(range(5)) + expected = [{0}, {1}, {2}, {3}, {4}] + assert nx.community.louvain_communities(G) == expected + + +def test_max_level(): + G = nx.LFR_benchmark_graph( + 250, 3, 1.5, 0.009, average_degree=5, min_community=20, seed=10 + ) + parts_iter = nx.community.louvain_partitions(G, seed=42) + for max_level, expected in enumerate(parts_iter, 1): + partition = nx.community.louvain_communities(G, max_level=max_level, seed=42) + assert partition == expected + assert max_level > 1 # Ensure we are actually testing max_level + # max_level is an upper limit; it's okay if we stop before it's hit. + partition = nx.community.louvain_communities(G, max_level=max_level + 1, seed=42) + assert partition == expected + with pytest.raises( + ValueError, match="max_level argument must be a positive integer" + ): + nx.community.louvain_communities(G, max_level=0) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa48f0f47667ce4c4fa96c175bee4cb95a4852f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_lukes.py @@ -0,0 +1,152 @@ +from itertools import product + +import pytest + +import networkx as nx + +EWL = "e_weight" +NWL = "n_weight" + + +# first test from the Lukes original paper +def paper_1_case(float_edge_wt=False, explicit_node_wt=True, directed=False): + # problem-specific constants + limit = 3 + + # configuration + if float_edge_wt: + shift = 0.001 + else: + shift = 0 + + if directed: + example_1 = nx.DiGraph() + else: + example_1 = nx.Graph() + + # graph creation + example_1.add_edge(1, 2, **{EWL: 3 + shift}) + example_1.add_edge(1, 4, **{EWL: 2 + shift}) + example_1.add_edge(2, 3, **{EWL: 4 + shift}) + example_1.add_edge(2, 5, **{EWL: 6 + shift}) + + # node weights + if explicit_node_wt: + nx.set_node_attributes(example_1, 1, NWL) + wtu = NWL + else: + wtu = None + + # partitioning + clusters_1 = { + frozenset(x) + for x in nx.community.lukes_partitioning( + example_1, limit, node_weight=wtu, edge_weight=EWL + ) + } + + return clusters_1 + + +# second test from the Lukes original paper +def paper_2_case(explicit_edge_wt=True, directed=False): + # problem specific constants + byte_block_size = 32 + + # configuration + if directed: + example_2 = nx.DiGraph() + else: + example_2 = nx.Graph() + + if explicit_edge_wt: + edic = {EWL: 1} + wtu = EWL + else: + edic = {} + wtu = None + + # graph creation + example_2.add_edge("name", "home_address", **edic) + example_2.add_edge("name", "education", **edic) + example_2.add_edge("education", "bs", **edic) + example_2.add_edge("education", "ms", **edic) + example_2.add_edge("education", "phd", **edic) + example_2.add_edge("name", "telephone", **edic) + example_2.add_edge("telephone", "home", **edic) + example_2.add_edge("telephone", "office", **edic) + example_2.add_edge("office", "no1", **edic) + example_2.add_edge("office", "no2", **edic) + + example_2.nodes["name"][NWL] = 20 + example_2.nodes["education"][NWL] = 10 + example_2.nodes["bs"][NWL] = 1 + example_2.nodes["ms"][NWL] = 1 + example_2.nodes["phd"][NWL] = 1 + example_2.nodes["home_address"][NWL] = 8 + example_2.nodes["telephone"][NWL] = 8 + example_2.nodes["home"][NWL] = 8 + example_2.nodes["office"][NWL] = 4 + example_2.nodes["no1"][NWL] = 1 + example_2.nodes["no2"][NWL] = 1 + + # partitioning + clusters_2 = { + frozenset(x) + for x in nx.community.lukes_partitioning( + example_2, byte_block_size, node_weight=NWL, edge_weight=wtu + ) + } + + return clusters_2 + + +def test_paper_1_case(): + ground_truth = {frozenset([1, 4]), frozenset([2, 3, 5])} + + tf = (True, False) + for flt, nwt, drc in product(tf, tf, tf): + part = paper_1_case(flt, nwt, drc) + assert part == ground_truth + + +def test_paper_2_case(): + ground_truth = { + frozenset(["education", "bs", "ms", "phd"]), + frozenset(["name", "home_address"]), + frozenset(["telephone", "home", "office", "no1", "no2"]), + } + + tf = (True, False) + for ewt, drc in product(tf, tf): + part = paper_2_case(ewt, drc) + assert part == ground_truth + + +def test_mandatory_tree(): + not_a_tree = nx.complete_graph(4) + + with pytest.raises(nx.NotATree): + nx.community.lukes_partitioning(not_a_tree, 5) + + +def test_mandatory_integrality(): + byte_block_size = 32 + + ex_1_broken = nx.DiGraph() + + ex_1_broken.add_edge(1, 2, **{EWL: 3.2}) + ex_1_broken.add_edge(1, 4, **{EWL: 2.4}) + ex_1_broken.add_edge(2, 3, **{EWL: 4.0}) + ex_1_broken.add_edge(2, 5, **{EWL: 6.3}) + + ex_1_broken.nodes[1][NWL] = 1.2 # ! + ex_1_broken.nodes[2][NWL] = 1 + ex_1_broken.nodes[3][NWL] = 1 + ex_1_broken.nodes[4][NWL] = 1 + ex_1_broken.nodes[5][NWL] = 2 + + with pytest.raises(TypeError): + nx.community.lukes_partitioning( + ex_1_broken, byte_block_size, node_weight=NWL, edge_weight=EWL + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py new file mode 100644 index 0000000000000000000000000000000000000000..0121367fc4ef766e2587610c3ea32ba33b12b259 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_modularity_max.py @@ -0,0 +1,340 @@ +import pytest + +import networkx as nx +from networkx.algorithms.community import ( + greedy_modularity_communities, + naive_greedy_modularity_communities, +) + + +@pytest.mark.parametrize( + "func", (greedy_modularity_communities, naive_greedy_modularity_communities) +) +def test_modularity_communities(func): + G = nx.karate_club_graph() + john_a = frozenset( + [8, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33] + ) + mr_hi = frozenset([0, 4, 5, 6, 10, 11, 16, 19]) + overlap = frozenset([1, 2, 3, 7, 9, 12, 13, 17, 21]) + expected = {john_a, overlap, mr_hi} + assert set(func(G, weight=None)) == expected + + +@pytest.mark.parametrize( + "func", (greedy_modularity_communities, naive_greedy_modularity_communities) +) +def test_modularity_communities_categorical_labels(func): + # Using other than 0-starting contiguous integers as node-labels. + G = nx.Graph( + [ + ("a", "b"), + ("a", "c"), + ("b", "c"), + ("b", "d"), # inter-community edge + ("d", "e"), + ("d", "f"), + ("d", "g"), + ("f", "g"), + ("d", "e"), + ("f", "e"), + ] + ) + expected = {frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})} + assert set(func(G)) == expected + + +def test_greedy_modularity_communities_components(): + # Test for gh-5530 + G = nx.Graph([(0, 1), (2, 3), (4, 5), (5, 6)]) + # usual case with 3 components + assert greedy_modularity_communities(G) == [{4, 5, 6}, {0, 1}, {2, 3}] + # best_n can make the algorithm continue even when modularity goes down + assert greedy_modularity_communities(G, best_n=3) == [{4, 5, 6}, {0, 1}, {2, 3}] + assert greedy_modularity_communities(G, best_n=2) == [{0, 1, 4, 5, 6}, {2, 3}] + assert greedy_modularity_communities(G, best_n=1) == [{0, 1, 2, 3, 4, 5, 6}] + + +def test_greedy_modularity_communities_relabeled(): + # Test for gh-4966 + G = nx.balanced_tree(2, 2) + mapping = {0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h"} + G = nx.relabel_nodes(G, mapping) + expected = [frozenset({"e", "d", "a", "b"}), frozenset({"c", "f", "g"})] + assert greedy_modularity_communities(G) == expected + + +def test_greedy_modularity_communities_directed(): + G = nx.DiGraph( + [ + ("a", "b"), + ("a", "c"), + ("b", "c"), + ("b", "d"), # inter-community edge + ("d", "e"), + ("d", "f"), + ("d", "g"), + ("f", "g"), + ("d", "e"), + ("f", "e"), + ] + ) + expected = [frozenset({"f", "g", "e", "d"}), frozenset({"a", "b", "c"})] + assert greedy_modularity_communities(G) == expected + + # with loops + G = nx.DiGraph() + G.add_edges_from( + [(1, 1), (1, 2), (1, 3), (2, 3), (1, 4), (4, 4), (5, 5), (4, 5), (4, 6), (5, 6)] + ) + expected = [frozenset({1, 2, 3}), frozenset({4, 5, 6})] + assert greedy_modularity_communities(G) == expected + + +@pytest.mark.parametrize( + "func", (greedy_modularity_communities, naive_greedy_modularity_communities) +) +def test_modularity_communities_weighted(func): + G = nx.balanced_tree(2, 3) + for a, b in G.edges: + if ((a == 1) or (a == 2)) and (b != 0): + G[a][b]["weight"] = 10.0 + else: + G[a][b]["weight"] = 1.0 + + expected = [{0, 1, 3, 4, 7, 8, 9, 10}, {2, 5, 6, 11, 12, 13, 14}] + + assert func(G, weight="weight") == expected + assert func(G, weight="weight", resolution=0.9) == expected + assert func(G, weight="weight", resolution=0.3) == expected + assert func(G, weight="weight", resolution=1.1) != expected + + +def test_modularity_communities_floating_point(): + # check for floating point error when used as key in the mapped_queue dict. + # Test for gh-4992 and gh-5000 + G = nx.Graph() + G.add_weighted_edges_from( + [(0, 1, 12), (1, 4, 71), (2, 3, 15), (2, 4, 10), (3, 6, 13)] + ) + expected = [{0, 1, 4}, {2, 3, 6}] + assert greedy_modularity_communities(G, weight="weight") == expected + assert ( + greedy_modularity_communities(G, weight="weight", resolution=0.99) == expected + ) + + +def test_modularity_communities_directed_weighted(): + G = nx.DiGraph() + G.add_weighted_edges_from( + [ + (1, 2, 5), + (1, 3, 3), + (2, 3, 6), + (2, 6, 1), + (1, 4, 1), + (4, 5, 3), + (4, 6, 7), + (5, 6, 2), + (5, 7, 5), + (5, 8, 4), + (6, 8, 3), + ] + ) + expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})] + assert greedy_modularity_communities(G, weight="weight") == expected + + # A large weight of the edge (2, 6) causes 6 to change group, even if it shares + # only one connection with the new group and 3 with the old one. + G[2][6]["weight"] = 20 + expected = [frozenset({1, 2, 3, 6}), frozenset({4, 5, 7, 8})] + assert greedy_modularity_communities(G, weight="weight") == expected + + +def test_greedy_modularity_communities_multigraph(): + G = nx.MultiGraph() + G.add_edges_from( + [ + (1, 2), + (1, 2), + (1, 3), + (2, 3), + (1, 4), + (2, 4), + (4, 5), + (5, 6), + (5, 7), + (5, 7), + (6, 7), + (7, 8), + (5, 8), + ] + ) + expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})] + assert greedy_modularity_communities(G) == expected + + # Converting (4, 5) into a multi-edge causes node 4 to change group. + G.add_edge(4, 5) + expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})] + assert greedy_modularity_communities(G) == expected + + +def test_greedy_modularity_communities_multigraph_weighted(): + G = nx.MultiGraph() + G.add_weighted_edges_from( + [ + (1, 2, 5), + (1, 2, 3), + (1, 3, 6), + (1, 3, 6), + (2, 3, 4), + (1, 4, 1), + (1, 4, 1), + (2, 4, 3), + (2, 4, 3), + (4, 5, 1), + (5, 6, 3), + (5, 6, 7), + (5, 6, 4), + (5, 7, 9), + (5, 7, 9), + (6, 7, 8), + (7, 8, 2), + (7, 8, 2), + (5, 8, 6), + (5, 8, 6), + ] + ) + expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})] + assert greedy_modularity_communities(G, weight="weight") == expected + + # Adding multi-edge (4, 5, 16) causes node 4 to change group. + G.add_edge(4, 5, weight=16) + expected = [frozenset({4, 5, 6, 7, 8}), frozenset({1, 2, 3})] + assert greedy_modularity_communities(G, weight="weight") == expected + + # Increasing the weight of edge (1, 4) causes node 4 to return to the former group. + G[1][4][1]["weight"] = 3 + expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})] + assert greedy_modularity_communities(G, weight="weight") == expected + + +def test_greed_modularity_communities_multidigraph(): + G = nx.MultiDiGraph() + G.add_edges_from( + [ + (1, 2), + (1, 2), + (3, 1), + (2, 3), + (2, 3), + (3, 2), + (1, 4), + (2, 4), + (4, 2), + (4, 5), + (5, 6), + (5, 6), + (6, 5), + (5, 7), + (6, 7), + (7, 8), + (5, 8), + (8, 4), + ] + ) + expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})] + assert greedy_modularity_communities(G, weight="weight") == expected + + +def test_greed_modularity_communities_multidigraph_weighted(): + G = nx.MultiDiGraph() + G.add_weighted_edges_from( + [ + (1, 2, 5), + (1, 2, 3), + (3, 1, 6), + (1, 3, 6), + (3, 2, 4), + (1, 4, 2), + (1, 4, 5), + (2, 4, 3), + (3, 2, 8), + (4, 2, 3), + (4, 3, 5), + (4, 5, 2), + (5, 6, 3), + (5, 6, 7), + (6, 5, 4), + (5, 7, 9), + (5, 7, 9), + (7, 6, 8), + (7, 8, 2), + (8, 7, 2), + (5, 8, 6), + (5, 8, 6), + ] + ) + expected = [frozenset({1, 2, 3, 4}), frozenset({5, 6, 7, 8})] + assert greedy_modularity_communities(G, weight="weight") == expected + + +def test_resolution_parameter_impact(): + G = nx.barbell_graph(5, 3) + + gamma = 1 + expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))] + assert greedy_modularity_communities(G, resolution=gamma) == expected + assert naive_greedy_modularity_communities(G, resolution=gamma) == expected + + gamma = 2.5 + expected = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}] + assert greedy_modularity_communities(G, resolution=gamma) == expected + assert naive_greedy_modularity_communities(G, resolution=gamma) == expected + + gamma = 0.3 + expected = [frozenset(range(8)), frozenset(range(8, 13))] + assert greedy_modularity_communities(G, resolution=gamma) == expected + assert naive_greedy_modularity_communities(G, resolution=gamma) == expected + + +def test_cutoff_parameter(): + G = nx.circular_ladder_graph(4) + + # No aggregation: + expected = [{k} for k in range(8)] + assert greedy_modularity_communities(G, cutoff=8) == expected + + # Aggregation to half order (number of nodes) + expected = [{k, k + 1} for k in range(0, 8, 2)] + assert greedy_modularity_communities(G, cutoff=4) == expected + + # Default aggregation case (here, 2 communities emerge) + expected = [frozenset(range(4)), frozenset(range(4, 8))] + assert greedy_modularity_communities(G, cutoff=1) == expected + + +def test_best_n(): + G = nx.barbell_graph(5, 3) + + # Same result as without enforcing cutoff: + best_n = 3 + expected = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))] + assert greedy_modularity_communities(G, best_n=best_n) == expected + + # One additional merging step: + best_n = 2 + expected = [frozenset(range(8)), frozenset(range(8, 13))] + assert greedy_modularity_communities(G, best_n=best_n) == expected + + # Two additional merging steps: + best_n = 1 + expected = [frozenset(range(13))] + assert greedy_modularity_communities(G, best_n=best_n) == expected + + +def test_greedy_modularity_communities_corner_cases(): + G = nx.empty_graph() + assert nx.community.greedy_modularity_communities(G) == [] + G.add_nodes_from(range(3)) + assert nx.community.greedy_modularity_communities(G) == [{0}, {1}, {2}] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py new file mode 100644 index 0000000000000000000000000000000000000000..c502c7e352114ec55c1b2bf716483014105b7068 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_quality.py @@ -0,0 +1,139 @@ +"""Unit tests for the :mod:`networkx.algorithms.community.quality` +module. + +""" + +import pytest + +import networkx as nx +from networkx import barbell_graph +from networkx.algorithms.community import modularity, partition_quality +from networkx.algorithms.community.quality import inter_community_edges + + +class TestPerformance: + """Unit tests for the :func:`performance` function.""" + + def test_bad_partition(self): + """Tests that a poor partition has a low performance measure.""" + G = barbell_graph(3, 0) + partition = [{0, 1, 4}, {2, 3, 5}] + assert 8 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7) + + def test_good_partition(self): + """Tests that a good partition has a high performance measure.""" + G = barbell_graph(3, 0) + partition = [{0, 1, 2}, {3, 4, 5}] + assert 14 / 15 == pytest.approx(partition_quality(G, partition)[1], abs=1e-7) + + +class TestCoverage: + """Unit tests for the :func:`coverage` function.""" + + def test_bad_partition(self): + """Tests that a poor partition has a low coverage measure.""" + G = barbell_graph(3, 0) + partition = [{0, 1, 4}, {2, 3, 5}] + assert 3 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7) + + def test_good_partition(self): + """Tests that a good partition has a high coverage measure.""" + G = barbell_graph(3, 0) + partition = [{0, 1, 2}, {3, 4, 5}] + assert 6 / 7 == pytest.approx(partition_quality(G, partition)[0], abs=1e-7) + + +def test_modularity(): + G = nx.barbell_graph(3, 0) + C = [{0, 1, 4}, {2, 3, 5}] + assert (-16 / (14**2)) == pytest.approx(modularity(G, C), abs=1e-7) + C = [{0, 1, 2}, {3, 4, 5}] + assert (35 * 2) / (14**2) == pytest.approx(modularity(G, C), abs=1e-7) + + n = 1000 + G = nx.erdos_renyi_graph(n, 0.09, seed=42, directed=True) + C = [set(range(n // 2)), set(range(n // 2, n))] + assert 0.00017154251389292754 == pytest.approx(modularity(G, C), abs=1e-7) + + G = nx.margulis_gabber_galil_graph(10) + mid_value = G.number_of_nodes() // 2 + nodes = list(G.nodes) + C = [set(nodes[:mid_value]), set(nodes[mid_value:])] + assert 0.13 == pytest.approx(modularity(G, C), abs=1e-7) + + G = nx.DiGraph() + G.add_edges_from([(2, 1), (2, 3), (3, 4)]) + C = [{1, 2}, {3, 4}] + assert 2 / 9 == pytest.approx(modularity(G, C), abs=1e-7) + + +def test_modularity_resolution(): + G = nx.barbell_graph(3, 0) + C = [{0, 1, 4}, {2, 3, 5}] + assert modularity(G, C) == pytest.approx(3 / 7 - 100 / 14**2) + gamma = 2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2) + gamma = 0.2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx(3 / 7 - gamma * 100 / 14**2) + + C = [{0, 1, 2}, {3, 4, 5}] + assert modularity(G, C) == pytest.approx(6 / 7 - 98 / 14**2) + gamma = 2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2) + gamma = 0.2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx(6 / 7 - gamma * 98 / 14**2) + + G = nx.barbell_graph(5, 3) + C = [frozenset(range(5)), frozenset(range(8, 13)), frozenset(range(5, 8))] + gamma = 1 + result = modularity(G, C, resolution=gamma) + # This C is maximal for gamma=1: modularity = 0.518229 + assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2))) + gamma = 2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2))) + gamma = 0.2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((22 / 24) - gamma * (918 / (48**2))) + + C = [{0, 1, 2, 3}, {9, 10, 11, 12}, {5, 6, 7}, {4}, {8}] + gamma = 1 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2))) + gamma = 2.5 + result = modularity(G, C, resolution=gamma) + # This C is maximal for gamma=2.5: modularity = -0.06553819 + assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2))) + gamma = 0.2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((14 / 24) - gamma * (598 / (48**2))) + + C = [frozenset(range(8)), frozenset(range(8, 13))] + gamma = 1 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2))) + gamma = 2 + result = modularity(G, C, resolution=gamma) + assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2))) + gamma = 0.3 + result = modularity(G, C, resolution=gamma) + # This C is maximal for gamma=0.3: modularity = 0.805990 + assert result == pytest.approx((23 / 24) - gamma * (1170 / (48**2))) + + +def test_inter_community_edges_with_digraphs(): + G = nx.complete_graph(2, create_using=nx.DiGraph()) + partition = [{0}, {1}] + assert inter_community_edges(G, partition) == 2 + + G = nx.complete_graph(10, create_using=nx.DiGraph()) + partition = [{0}, {1, 2}, {3, 4, 5}, {6, 7, 8, 9}] + assert inter_community_edges(G, partition) == 70 + + G = nx.cycle_graph(4, create_using=nx.DiGraph()) + partition = [{0, 1}, {2, 3}] + assert inter_community_edges(G, partition) == 2 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ea019db9da8ae0eaacb9428845125515c36ff46a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/community/tests/test_utils.py @@ -0,0 +1,26 @@ +"""Unit tests for the :mod:`networkx.algorithms.community.utils` module.""" + +import networkx as nx + + +def test_is_partition(): + G = nx.empty_graph(3) + assert nx.community.is_partition(G, [{0, 1}, {2}]) + assert nx.community.is_partition(G, ({0, 1}, {2})) + assert nx.community.is_partition(G, ([0, 1], [2])) + assert nx.community.is_partition(G, [[0, 1], [2]]) + + +def test_not_covering(): + G = nx.empty_graph(3) + assert not nx.community.is_partition(G, [{0}, {1}]) + + +def test_not_disjoint(): + G = nx.empty_graph(3) + assert not nx.community.is_partition(G, [{0, 1}, {1, 2}]) + + +def test_not_node(): + G = nx.empty_graph(3) + assert not nx.community.is_partition(G, [{0, 1}, {3}]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f67dfdefa6de8f26003ceac0c23550286bff76a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/attracting.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/attracting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b2c130ce00565c5257e57a86ad28ed02eb2e1df3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/attracting.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/biconnected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/biconnected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed544d9a072998e1639e1d15b43fe6d3db65b8ed Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/biconnected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46552476e988269f0b694816c1eccaceaf956240 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/semiconnected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/semiconnected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1b2443af98c784e602aec83967da703a29c9b8eb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/semiconnected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/strongly_connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/strongly_connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdc03c81f7b0f725529aee1bc9493542bbd15dc4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/strongly_connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/weakly_connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/weakly_connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5da0320da9ab8823c448aee59f2d27fb94099f2d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/__pycache__/weakly_connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/biconnected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/biconnected.py new file mode 100644 index 0000000000000000000000000000000000000000..fd0f3865bb18e9c9eb37d768c7fd3caceb1cde86 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/biconnected.py @@ -0,0 +1,394 @@ +"""Biconnected components and articulation points.""" + +from itertools import chain + +import networkx as nx +from networkx.utils.decorators import not_implemented_for + +__all__ = [ + "biconnected_components", + "biconnected_component_edges", + "is_biconnected", + "articulation_points", +] + + +@not_implemented_for("directed") +@nx._dispatchable +def is_biconnected(G): + """Returns True if the graph is biconnected, False otherwise. + + A graph is biconnected if, and only if, it cannot be disconnected by + removing only one node (and all edges incident on that node). If + removing a node increases the number of disconnected components + in the graph, that node is called an articulation point, or cut + vertex. A biconnected graph has no articulation points. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + biconnected : bool + True if the graph is biconnected, False otherwise. + + Raises + ------ + NetworkXNotImplemented + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> print(nx.is_biconnected(G)) + False + >>> G.add_edge(0, 3) + >>> print(nx.is_biconnected(G)) + True + + See Also + -------- + biconnected_components + articulation_points + biconnected_component_edges + is_strongly_connected + is_weakly_connected + is_connected + is_semiconnected + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + + """ + bccs = biconnected_components(G) + try: + bcc = next(bccs) + except StopIteration: + # No bicomponents (empty graph?) + return False + try: + next(bccs) + except StopIteration: + # Only one bicomponent + return len(bcc) == len(G) + else: + # Multiple bicomponents + return False + + +@not_implemented_for("directed") +@nx._dispatchable +def biconnected_component_edges(G): + """Returns a generator of lists of edges, one list for each biconnected + component of the input graph. + + Biconnected components are maximal subgraphs such that the removal of a + node (and all edges incident on that node) will not disconnect the + subgraph. Note that nodes may be part of more than one biconnected + component. Those nodes are articulation points, or cut vertices. + However, each edge belongs to one, and only one, biconnected component. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + edges : generator of lists + Generator of lists of edges, one list for each bicomponent. + + Raises + ------ + NetworkXNotImplemented + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.barbell_graph(4, 2) + >>> print(nx.is_biconnected(G)) + False + >>> bicomponents_edges = list(nx.biconnected_component_edges(G)) + >>> len(bicomponents_edges) + 5 + >>> G.add_edge(2, 8) + >>> print(nx.is_biconnected(G)) + True + >>> bicomponents_edges = list(nx.biconnected_component_edges(G)) + >>> len(bicomponents_edges) + 1 + + See Also + -------- + is_biconnected, + biconnected_components, + articulation_points, + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + + """ + yield from _biconnected_dfs(G, components=True) + + +@not_implemented_for("directed") +@nx._dispatchable +def biconnected_components(G): + """Returns a generator of sets of nodes, one set for each biconnected + component of the graph + + Biconnected components are maximal subgraphs such that the removal of a + node (and all edges incident on that node) will not disconnect the + subgraph. Note that nodes may be part of more than one biconnected + component. Those nodes are articulation points, or cut vertices. The + removal of articulation points will increase the number of connected + components of the graph. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Returns + ------- + nodes : generator + Generator of sets of nodes, one set for each biconnected component. + + Raises + ------ + NetworkXNotImplemented + If the input graph is not undirected. + + Examples + -------- + >>> G = nx.lollipop_graph(5, 1) + >>> print(nx.is_biconnected(G)) + False + >>> bicomponents = list(nx.biconnected_components(G)) + >>> len(bicomponents) + 2 + >>> G.add_edge(0, 5) + >>> print(nx.is_biconnected(G)) + True + >>> bicomponents = list(nx.biconnected_components(G)) + >>> len(bicomponents) + 1 + + You can generate a sorted list of biconnected components, largest + first, using sort. + + >>> G.remove_edge(0, 5) + >>> [len(c) for c in sorted(nx.biconnected_components(G), key=len, reverse=True)] + [5, 2] + + If you only want the largest connected component, it's more + efficient to use max instead of sort. + + >>> Gc = max(nx.biconnected_components(G), key=len) + + To create the components as subgraphs use: + ``(G.subgraph(c).copy() for c in biconnected_components(G))`` + + See Also + -------- + is_biconnected + articulation_points + biconnected_component_edges + k_components : this function is a special case where k=2 + bridge_components : similar to this function, but is defined using + 2-edge-connectivity instead of 2-node-connectivity. + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + + """ + for comp in _biconnected_dfs(G, components=True): + yield set(chain.from_iterable(comp)) + + +@not_implemented_for("directed") +@nx._dispatchable +def articulation_points(G): + """Yield the articulation points, or cut vertices, of a graph. + + An articulation point or cut vertex is any node whose removal (along with + all its incident edges) increases the number of connected components of + a graph. An undirected connected graph without articulation points is + biconnected. Articulation points belong to more than one biconnected + component of a graph. + + Notice that by convention a dyad is considered a biconnected component. + + Parameters + ---------- + G : NetworkX Graph + An undirected graph. + + Yields + ------ + node + An articulation point in the graph. + + Raises + ------ + NetworkXNotImplemented + If the input graph is not undirected. + + Examples + -------- + + >>> G = nx.barbell_graph(4, 2) + >>> print(nx.is_biconnected(G)) + False + >>> len(list(nx.articulation_points(G))) + 4 + >>> G.add_edge(2, 8) + >>> print(nx.is_biconnected(G)) + True + >>> len(list(nx.articulation_points(G))) + 0 + + See Also + -------- + is_biconnected + biconnected_components + biconnected_component_edges + + Notes + ----- + The algorithm to find articulation points and biconnected + components is implemented using a non-recursive depth-first-search + (DFS) that keeps track of the highest level that back edges reach + in the DFS tree. A node `n` is an articulation point if, and only + if, there exists a subtree rooted at `n` such that there is no + back edge from any successor of `n` that links to a predecessor of + `n` in the DFS tree. By keeping track of all the edges traversed + by the DFS we can obtain the biconnected components because all + edges of a bicomponent will be traversed consecutively between + articulation points. + + References + ---------- + .. [1] Hopcroft, J.; Tarjan, R. (1973). + "Efficient algorithms for graph manipulation". + Communications of the ACM 16: 372–378. doi:10.1145/362248.362272 + + """ + seen = set() + for articulation in _biconnected_dfs(G, components=False): + if articulation not in seen: + seen.add(articulation) + yield articulation + + +@not_implemented_for("directed") +def _biconnected_dfs(G, components=True): + # depth-first search algorithm to generate articulation points + # and biconnected components + visited = set() + for start in G: + if start in visited: + continue + discovery = {start: 0} # time of first discovery of node during search + low = {start: 0} + root_children = 0 + visited.add(start) + edge_stack = [] + stack = [(start, start, iter(G[start]))] + edge_index = {} + while stack: + grandparent, parent, children = stack[-1] + try: + child = next(children) + if grandparent == child: + continue + if child in visited: + if discovery[child] <= discovery[parent]: # back edge + low[parent] = min(low[parent], discovery[child]) + if components: + edge_index[parent, child] = len(edge_stack) + edge_stack.append((parent, child)) + else: + low[child] = discovery[child] = len(discovery) + visited.add(child) + stack.append((parent, child, iter(G[child]))) + if components: + edge_index[parent, child] = len(edge_stack) + edge_stack.append((parent, child)) + + except StopIteration: + stack.pop() + if len(stack) > 1: + if low[parent] >= discovery[grandparent]: + if components: + ind = edge_index[grandparent, parent] + yield edge_stack[ind:] + del edge_stack[ind:] + + else: + yield grandparent + low[grandparent] = min(low[parent], low[grandparent]) + elif stack: # length 1 so grandparent is root + root_children += 1 + if components: + ind = edge_index[grandparent, parent] + yield edge_stack[ind:] + del edge_stack[ind:] + if not components: + # root node is articulation point if it has more than 1 child + if root_children > 1: + yield start diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/semiconnected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/semiconnected.py new file mode 100644 index 0000000000000000000000000000000000000000..9ca5d762ca882524d1406f9295fa3a238fedb724 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/semiconnected.py @@ -0,0 +1,71 @@ +"""Semiconnectedness.""" + +import networkx as nx +from networkx.utils import not_implemented_for, pairwise + +__all__ = ["is_semiconnected"] + + +@not_implemented_for("undirected") +@nx._dispatchable +def is_semiconnected(G): + r"""Returns True if the graph is semiconnected, False otherwise. + + A graph is semiconnected if and only if for any pair of nodes, either one + is reachable from the other, or they are mutually reachable. + + This function uses a theorem that states that a DAG is semiconnected + if for any topological sort, for node $v_n$ in that sort, there is an + edge $(v_i, v_{i+1})$. That allows us to check if a non-DAG `G` is + semiconnected by condensing the graph: i.e. constructing a new graph `H` + with nodes being the strongly connected components of `G`, and edges + (scc_1, scc_2) if there is a edge $(v_1, v_2)$ in `G` for some + $v_1 \in scc_1$ and $v_2 \in scc_2$. That results in a DAG, so we compute + the topological sort of `H` and check if for every $n$ there is an edge + $(scc_n, scc_{n+1})$. + + Parameters + ---------- + G : NetworkX graph + A directed graph. + + Returns + ------- + semiconnected : bool + True if the graph is semiconnected, False otherwise. + + Raises + ------ + NetworkXNotImplemented + If the input graph is undirected. + + NetworkXPointlessConcept + If the graph is empty. + + Examples + -------- + >>> G = nx.path_graph(4, create_using=nx.DiGraph()) + >>> print(nx.is_semiconnected(G)) + True + >>> G = nx.DiGraph([(1, 2), (3, 2)]) + >>> print(nx.is_semiconnected(G)) + False + + See Also + -------- + is_strongly_connected + is_weakly_connected + is_connected + is_biconnected + """ + if len(G) == 0: + raise nx.NetworkXPointlessConcept( + "Connectivity is undefined for the null graph." + ) + + if not nx.is_weakly_connected(G): + return False + + H = nx.condensation(G) + + return all(H.has_edge(u, v) for u, v in pairwise(nx.topological_sort(H))) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99f30b8605de2658cc2b1993b2478ecf6f438b2b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_attracting.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_attracting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd223f27b362c72cc7d226892ab0dabafbbc123e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_attracting.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_biconnected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_biconnected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00b65e74183b3a100a52cedca2816e4e82aba58d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_biconnected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d41fe2624fafa7af192d206d21fdd7500236d8c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_semiconnected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_semiconnected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3108b1c8ca3a4627cab1bd88c3aa86f675c9e6f7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_semiconnected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_strongly_connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_strongly_connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..536b72f19485688810bd2d27054ece85dafc68db Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_strongly_connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_weakly_connected.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_weakly_connected.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5aac16e80906191bbc14c167b568d5e2d67574be Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/__pycache__/test_weakly_connected.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_attracting.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_attracting.py new file mode 100644 index 0000000000000000000000000000000000000000..336c40ddc27162c1c2f5cc245f4fc840311506b5 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_attracting.py @@ -0,0 +1,70 @@ +import pytest + +import networkx as nx +from networkx import NetworkXNotImplemented + + +class TestAttractingComponents: + @classmethod + def setup_class(cls): + cls.G1 = nx.DiGraph() + cls.G1.add_edges_from( + [ + (5, 11), + (11, 2), + (11, 9), + (11, 10), + (7, 11), + (7, 8), + (8, 9), + (3, 8), + (3, 10), + ] + ) + cls.G2 = nx.DiGraph() + cls.G2.add_edges_from([(0, 1), (0, 2), (1, 1), (1, 2), (2, 1)]) + + cls.G3 = nx.DiGraph() + cls.G3.add_edges_from([(0, 1), (1, 2), (2, 1), (0, 3), (3, 4), (4, 3)]) + + cls.G4 = nx.DiGraph() + + def test_attracting_components(self): + ac = list(nx.attracting_components(self.G1)) + assert {2} in ac + assert {9} in ac + assert {10} in ac + + ac = list(nx.attracting_components(self.G2)) + ac = [tuple(sorted(x)) for x in ac] + assert ac == [(1, 2)] + + ac = list(nx.attracting_components(self.G3)) + ac = [tuple(sorted(x)) for x in ac] + assert (1, 2) in ac + assert (3, 4) in ac + assert len(ac) == 2 + + ac = list(nx.attracting_components(self.G4)) + assert ac == [] + + def test_number_attacting_components(self): + assert nx.number_attracting_components(self.G1) == 3 + assert nx.number_attracting_components(self.G2) == 1 + assert nx.number_attracting_components(self.G3) == 2 + assert nx.number_attracting_components(self.G4) == 0 + + def test_is_attracting_component(self): + assert not nx.is_attracting_component(self.G1) + assert not nx.is_attracting_component(self.G2) + assert not nx.is_attracting_component(self.G3) + g2 = self.G3.subgraph([1, 2]) + assert nx.is_attracting_component(g2) + assert not nx.is_attracting_component(self.G4) + + def test_connected_raise(self): + G = nx.Graph() + with pytest.raises(NetworkXNotImplemented): + next(nx.attracting_components(G)) + pytest.raises(NetworkXNotImplemented, nx.number_attracting_components, G) + pytest.raises(NetworkXNotImplemented, nx.is_attracting_component, G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_biconnected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_biconnected.py new file mode 100644 index 0000000000000000000000000000000000000000..19d2d8831ced26a516d101e735b6701f39865c1b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_biconnected.py @@ -0,0 +1,248 @@ +import pytest + +import networkx as nx +from networkx import NetworkXNotImplemented + + +def assert_components_edges_equal(x, y): + sx = {frozenset(frozenset(e) for e in c) for c in x} + sy = {frozenset(frozenset(e) for e in c) for c in y} + assert sx == sy + + +def assert_components_equal(x, y): + sx = {frozenset(c) for c in x} + sy = {frozenset(c) for c in y} + assert sx == sy + + +def test_barbell(): + G = nx.barbell_graph(8, 4) + nx.add_path(G, [7, 20, 21, 22]) + nx.add_cycle(G, [22, 23, 24, 25]) + pts = set(nx.articulation_points(G)) + assert pts == {7, 8, 9, 10, 11, 12, 20, 21, 22} + + answer = [ + {12, 13, 14, 15, 16, 17, 18, 19}, + {0, 1, 2, 3, 4, 5, 6, 7}, + {22, 23, 24, 25}, + {11, 12}, + {10, 11}, + {9, 10}, + {8, 9}, + {7, 8}, + {21, 22}, + {20, 21}, + {7, 20}, + ] + assert_components_equal(list(nx.biconnected_components(G)), answer) + + G.add_edge(2, 17) + pts = set(nx.articulation_points(G)) + assert pts == {7, 20, 21, 22} + + +def test_articulation_points_repetitions(): + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (1, 3)]) + assert list(nx.articulation_points(G)) == [1] + + +def test_articulation_points_cycle(): + G = nx.cycle_graph(3) + nx.add_cycle(G, [1, 3, 4]) + pts = set(nx.articulation_points(G)) + assert pts == {1} + + +def test_is_biconnected(): + G = nx.cycle_graph(3) + assert nx.is_biconnected(G) + nx.add_cycle(G, [1, 3, 4]) + assert not nx.is_biconnected(G) + + +def test_empty_is_biconnected(): + G = nx.empty_graph(5) + assert not nx.is_biconnected(G) + G.add_edge(0, 1) + assert not nx.is_biconnected(G) + + +def test_biconnected_components_cycle(): + G = nx.cycle_graph(3) + nx.add_cycle(G, [1, 3, 4]) + answer = [{0, 1, 2}, {1, 3, 4}] + assert_components_equal(list(nx.biconnected_components(G)), answer) + + +def test_biconnected_components1(): + # graph example from + # https://web.archive.org/web/20121229123447/http://www.ibluemojo.com/school/articul_algorithm.html + edges = [ + (0, 1), + (0, 5), + (0, 6), + (0, 14), + (1, 5), + (1, 6), + (1, 14), + (2, 4), + (2, 10), + (3, 4), + (3, 15), + (4, 6), + (4, 7), + (4, 10), + (5, 14), + (6, 14), + (7, 9), + (8, 9), + (8, 12), + (8, 13), + (10, 15), + (11, 12), + (11, 13), + (12, 13), + ] + G = nx.Graph(edges) + pts = set(nx.articulation_points(G)) + assert pts == {4, 6, 7, 8, 9} + comps = list(nx.biconnected_component_edges(G)) + answer = [ + [(3, 4), (15, 3), (10, 15), (10, 4), (2, 10), (4, 2)], + [(13, 12), (13, 8), (11, 13), (12, 11), (8, 12)], + [(9, 8)], + [(7, 9)], + [(4, 7)], + [(6, 4)], + [(14, 0), (5, 1), (5, 0), (14, 5), (14, 1), (6, 14), (6, 0), (1, 6), (0, 1)], + ] + assert_components_edges_equal(comps, answer) + + +def test_biconnected_components2(): + G = nx.Graph() + nx.add_cycle(G, "ABC") + nx.add_cycle(G, "CDE") + nx.add_cycle(G, "FIJHG") + nx.add_cycle(G, "GIJ") + G.add_edge("E", "G") + comps = list(nx.biconnected_component_edges(G)) + answer = [ + [ + tuple("GF"), + tuple("FI"), + tuple("IG"), + tuple("IJ"), + tuple("JG"), + tuple("JH"), + tuple("HG"), + ], + [tuple("EG")], + [tuple("CD"), tuple("DE"), tuple("CE")], + [tuple("AB"), tuple("BC"), tuple("AC")], + ] + assert_components_edges_equal(comps, answer) + + +def test_biconnected_davis(): + D = nx.davis_southern_women_graph() + bcc = list(nx.biconnected_components(D))[0] + assert set(D) == bcc # All nodes in a giant bicomponent + # So no articulation points + assert len(list(nx.articulation_points(D))) == 0 + + +def test_biconnected_karate(): + K = nx.karate_club_graph() + answer = [ + { + 0, + 1, + 2, + 3, + 7, + 8, + 9, + 12, + 13, + 14, + 15, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + }, + {0, 4, 5, 6, 10, 16}, + {0, 11}, + ] + bcc = list(nx.biconnected_components(K)) + assert_components_equal(bcc, answer) + assert set(nx.articulation_points(K)) == {0} + + +def test_biconnected_eppstein(): + # tests from http://www.ics.uci.edu/~eppstein/PADS/Biconnectivity.py + G1 = nx.Graph( + { + 0: [1, 2, 5], + 1: [0, 5], + 2: [0, 3, 4], + 3: [2, 4, 5, 6], + 4: [2, 3, 5, 6], + 5: [0, 1, 3, 4], + 6: [3, 4], + } + ) + G2 = nx.Graph( + { + 0: [2, 5], + 1: [3, 8], + 2: [0, 3, 5], + 3: [1, 2, 6, 8], + 4: [7], + 5: [0, 2], + 6: [3, 8], + 7: [4], + 8: [1, 3, 6], + } + ) + assert nx.is_biconnected(G1) + assert not nx.is_biconnected(G2) + answer_G2 = [{1, 3, 6, 8}, {0, 2, 5}, {2, 3}, {4, 7}] + bcc = list(nx.biconnected_components(G2)) + assert_components_equal(bcc, answer_G2) + + +def test_null_graph(): + G = nx.Graph() + assert not nx.is_biconnected(G) + assert list(nx.biconnected_components(G)) == [] + assert list(nx.biconnected_component_edges(G)) == [] + assert list(nx.articulation_points(G)) == [] + + +def test_connected_raise(): + DG = nx.DiGraph() + with pytest.raises(NetworkXNotImplemented): + next(nx.biconnected_components(DG)) + with pytest.raises(NetworkXNotImplemented): + next(nx.biconnected_component_edges(DG)) + with pytest.raises(NetworkXNotImplemented): + next(nx.articulation_points(DG)) + pytest.raises(NetworkXNotImplemented, nx.is_biconnected, DG) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_connected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_connected.py new file mode 100644 index 0000000000000000000000000000000000000000..207214c1262ed58ac1152a5917a270514748dc0a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_connected.py @@ -0,0 +1,138 @@ +import pytest + +import networkx as nx +from networkx import NetworkXNotImplemented +from networkx import convert_node_labels_to_integers as cnlti +from networkx.classes.tests import dispatch_interface + + +class TestConnected: + @classmethod + def setup_class(cls): + G1 = cnlti(nx.grid_2d_graph(2, 2), first_label=0, ordering="sorted") + G2 = cnlti(nx.lollipop_graph(3, 3), first_label=4, ordering="sorted") + G3 = cnlti(nx.house_graph(), first_label=10, ordering="sorted") + cls.G = nx.union(G1, G2) + cls.G = nx.union(cls.G, G3) + cls.DG = nx.DiGraph([(1, 2), (1, 3), (2, 3)]) + cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1) + + cls.gc = [] + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2), + (2, 3), + (2, 8), + (3, 4), + (3, 7), + (4, 5), + (5, 3), + (5, 6), + (7, 4), + (7, 6), + (8, 1), + (8, 7), + ] + ) + C = [[3, 4, 5, 7], [1, 2, 8], [6]] + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)]) + C = [[2, 3, 4], [1]] + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)]) + C = [[1, 2, 3]] + cls.gc.append((G, C)) + + # Eppstein's tests + G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []}) + C = [[0], [1], [2], [3], [4], [5], [6]] + cls.gc.append((G, C)) + + G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]}) + C = [[0, 1, 2], [3, 4]] + cls.gc.append((G, C)) + + G = nx.DiGraph() + C = [] + cls.gc.append((G, C)) + + def test_connected_components(self): + # Test duplicated below + cc = nx.connected_components + G = self.G + C = { + frozenset([0, 1, 2, 3]), + frozenset([4, 5, 6, 7, 8, 9]), + frozenset([10, 11, 12, 13, 14]), + } + assert {frozenset(g) for g in cc(G)} == C + + def test_connected_components_nx_loopback(self): + # This tests the @nx._dispatchable mechanism, treating nx.connected_components + # as if it were a re-implementation from another package. + # Test duplicated from above + cc = nx.connected_components + G = dispatch_interface.convert(self.G) + C = { + frozenset([0, 1, 2, 3]), + frozenset([4, 5, 6, 7, 8, 9]), + frozenset([10, 11, 12, 13, 14]), + } + if "nx_loopback" in nx.config.backends or not nx.config.backends: + # If `nx.config.backends` is empty, then `_dispatchable.__call__` takes a + # "fast path" and does not check graph inputs, so using an unknown backend + # here will still work. + assert {frozenset(g) for g in cc(G)} == C + else: + # This raises, because "nx_loopback" is not registered as a backend. + with pytest.raises( + ImportError, match="'nx_loopback' backend is not installed" + ): + cc(G) + + def test_number_connected_components(self): + ncc = nx.number_connected_components + assert ncc(self.G) == 3 + + def test_number_connected_components2(self): + ncc = nx.number_connected_components + assert ncc(self.grid) == 1 + + def test_connected_components2(self): + cc = nx.connected_components + G = self.grid + C = {frozenset([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])} + assert {frozenset(g) for g in cc(G)} == C + + def test_node_connected_components(self): + ncc = nx.node_connected_component + G = self.grid + C = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + assert ncc(G, 1) == C + + def test_is_connected(self): + assert nx.is_connected(self.grid) + G = nx.Graph() + G.add_nodes_from([1, 2]) + assert not nx.is_connected(G) + + def test_connected_raise(self): + with pytest.raises(NetworkXNotImplemented): + next(nx.connected_components(self.DG)) + pytest.raises(NetworkXNotImplemented, nx.number_connected_components, self.DG) + pytest.raises(NetworkXNotImplemented, nx.node_connected_component, self.DG, 1) + pytest.raises(NetworkXNotImplemented, nx.is_connected, self.DG) + pytest.raises(nx.NetworkXPointlessConcept, nx.is_connected, nx.Graph()) + + def test_connected_mutability(self): + G = self.grid + seen = set() + for component in nx.connected_components(G): + assert len(seen & component) == 0 + seen.update(component) + component.clear() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_semiconnected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_semiconnected.py new file mode 100644 index 0000000000000000000000000000000000000000..6376bbfb12a061e1724b0c74d2614e116149d8bf --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_semiconnected.py @@ -0,0 +1,55 @@ +from itertools import chain + +import pytest + +import networkx as nx + + +class TestIsSemiconnected: + def test_undirected(self): + pytest.raises(nx.NetworkXNotImplemented, nx.is_semiconnected, nx.Graph()) + pytest.raises(nx.NetworkXNotImplemented, nx.is_semiconnected, nx.MultiGraph()) + + def test_empty(self): + pytest.raises(nx.NetworkXPointlessConcept, nx.is_semiconnected, nx.DiGraph()) + pytest.raises( + nx.NetworkXPointlessConcept, nx.is_semiconnected, nx.MultiDiGraph() + ) + + def test_single_node_graph(self): + G = nx.DiGraph() + G.add_node(0) + assert nx.is_semiconnected(G) + + def test_path(self): + G = nx.path_graph(100, create_using=nx.DiGraph()) + assert nx.is_semiconnected(G) + G.add_edge(100, 99) + assert not nx.is_semiconnected(G) + + def test_cycle(self): + G = nx.cycle_graph(100, create_using=nx.DiGraph()) + assert nx.is_semiconnected(G) + G = nx.path_graph(100, create_using=nx.DiGraph()) + G.add_edge(0, 99) + assert nx.is_semiconnected(G) + + def test_tree(self): + G = nx.DiGraph() + G.add_edges_from( + chain.from_iterable([(i, 2 * i + 1), (i, 2 * i + 2)] for i in range(100)) + ) + assert not nx.is_semiconnected(G) + + def test_dumbbell(self): + G = nx.cycle_graph(100, create_using=nx.DiGraph()) + G.add_edges_from((i + 100, (i + 1) % 100 + 100) for i in range(100)) + assert not nx.is_semiconnected(G) # G is disconnected. + G.add_edge(100, 99) + assert nx.is_semiconnected(G) + + def test_alternating_path(self): + G = nx.DiGraph( + chain.from_iterable([(i, i - 1), (i, i + 1)] for i in range(0, 100, 2)) + ) + assert not nx.is_semiconnected(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_strongly_connected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_strongly_connected.py new file mode 100644 index 0000000000000000000000000000000000000000..27f40988265b61eec9edb2bde64433f7396022f0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_strongly_connected.py @@ -0,0 +1,193 @@ +import pytest + +import networkx as nx +from networkx import NetworkXNotImplemented + + +class TestStronglyConnected: + @classmethod + def setup_class(cls): + cls.gc = [] + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2), + (2, 3), + (2, 8), + (3, 4), + (3, 7), + (4, 5), + (5, 3), + (5, 6), + (7, 4), + (7, 6), + (8, 1), + (8, 7), + ] + ) + C = {frozenset([3, 4, 5, 7]), frozenset([1, 2, 8]), frozenset([6])} + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)]) + C = {frozenset([2, 3, 4]), frozenset([1])} + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)]) + C = {frozenset([1, 2, 3])} + cls.gc.append((G, C)) + + # Eppstein's tests + G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []}) + C = { + frozenset([0]), + frozenset([1]), + frozenset([2]), + frozenset([3]), + frozenset([4]), + frozenset([5]), + frozenset([6]), + } + cls.gc.append((G, C)) + + G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]}) + C = {frozenset([0, 1, 2]), frozenset([3, 4])} + cls.gc.append((G, C)) + + def test_tarjan(self): + scc = nx.strongly_connected_components + for G, C in self.gc: + assert {frozenset(g) for g in scc(G)} == C + + def test_kosaraju(self): + scc = nx.kosaraju_strongly_connected_components + for G, C in self.gc: + assert {frozenset(g) for g in scc(G)} == C + + def test_number_strongly_connected_components(self): + ncc = nx.number_strongly_connected_components + for G, C in self.gc: + assert ncc(G) == len(C) + + def test_is_strongly_connected(self): + for G, C in self.gc: + if len(C) == 1: + assert nx.is_strongly_connected(G) + else: + assert not nx.is_strongly_connected(G) + + def test_contract_scc1(self): + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2), + (2, 3), + (2, 11), + (2, 12), + (3, 4), + (4, 3), + (4, 5), + (5, 6), + (6, 5), + (6, 7), + (7, 8), + (7, 9), + (7, 10), + (8, 9), + (9, 7), + (10, 6), + (11, 2), + (11, 4), + (11, 6), + (12, 6), + (12, 11), + ] + ) + scc = list(nx.strongly_connected_components(G)) + cG = nx.condensation(G, scc) + # DAG + assert nx.is_directed_acyclic_graph(cG) + # nodes + assert sorted(cG.nodes()) == [0, 1, 2, 3] + # edges + mapping = {} + for i, component in enumerate(scc): + for n in component: + mapping[n] = i + edge = (mapping[2], mapping[3]) + assert cG.has_edge(*edge) + edge = (mapping[2], mapping[5]) + assert cG.has_edge(*edge) + edge = (mapping[3], mapping[5]) + assert cG.has_edge(*edge) + + def test_contract_scc_isolate(self): + # Bug found and fixed in [1687]. + G = nx.DiGraph() + G.add_edge(1, 2) + G.add_edge(2, 1) + scc = list(nx.strongly_connected_components(G)) + cG = nx.condensation(G, scc) + assert list(cG.nodes()) == [0] + assert list(cG.edges()) == [] + + def test_contract_scc_edge(self): + G = nx.DiGraph() + G.add_edge(1, 2) + G.add_edge(2, 1) + G.add_edge(2, 3) + G.add_edge(3, 4) + G.add_edge(4, 3) + scc = list(nx.strongly_connected_components(G)) + cG = nx.condensation(G, scc) + assert sorted(cG.nodes()) == [0, 1] + if 1 in scc[0]: + edge = (0, 1) + else: + edge = (1, 0) + assert list(cG.edges()) == [edge] + + def test_condensation_mapping_and_members(self): + G, C = self.gc[1] + C = sorted(C, key=len, reverse=True) + cG = nx.condensation(G) + mapping = cG.graph["mapping"] + assert all(n in G for n in mapping) + assert all(0 == cN for n, cN in mapping.items() if n in C[0]) + assert all(1 == cN for n, cN in mapping.items() if n in C[1]) + for n, d in cG.nodes(data=True): + assert set(C[n]) == cG.nodes[n]["members"] + + def test_null_graph(self): + G = nx.DiGraph() + assert list(nx.strongly_connected_components(G)) == [] + assert list(nx.kosaraju_strongly_connected_components(G)) == [] + assert len(nx.condensation(G)) == 0 + pytest.raises( + nx.NetworkXPointlessConcept, nx.is_strongly_connected, nx.DiGraph() + ) + + def test_connected_raise(self): + G = nx.Graph() + with pytest.raises(NetworkXNotImplemented): + next(nx.strongly_connected_components(G)) + with pytest.raises(NetworkXNotImplemented): + next(nx.kosaraju_strongly_connected_components(G)) + pytest.raises(NetworkXNotImplemented, nx.is_strongly_connected, G) + pytest.raises(NetworkXNotImplemented, nx.condensation, G) + + strong_cc_methods = ( + nx.strongly_connected_components, + nx.kosaraju_strongly_connected_components, + ) + + @pytest.mark.parametrize("get_components", strong_cc_methods) + def test_connected_mutability(self, get_components): + DG = nx.path_graph(5, create_using=nx.DiGraph) + G = nx.disjoint_union(DG, DG) + seen = set() + for component in get_components(G): + assert len(seen & component) == 0 + seen.update(component) + component.clear() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_weakly_connected.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_weakly_connected.py new file mode 100644 index 0000000000000000000000000000000000000000..f014478930f598b02e6852e3109978288d023dfc --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/components/tests/test_weakly_connected.py @@ -0,0 +1,96 @@ +import pytest + +import networkx as nx +from networkx import NetworkXNotImplemented + + +class TestWeaklyConnected: + @classmethod + def setup_class(cls): + cls.gc = [] + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2), + (2, 3), + (2, 8), + (3, 4), + (3, 7), + (4, 5), + (5, 3), + (5, 6), + (7, 4), + (7, 6), + (8, 1), + (8, 7), + ] + ) + C = [[3, 4, 5, 7], [1, 2, 8], [6]] + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (1, 3), (1, 4), (4, 2), (3, 4), (2, 3)]) + C = [[2, 3, 4], [1]] + cls.gc.append((G, C)) + + G = nx.DiGraph() + G.add_edges_from([(1, 2), (2, 3), (3, 2), (2, 1)]) + C = [[1, 2, 3]] + cls.gc.append((G, C)) + + # Eppstein's tests + G = nx.DiGraph({0: [1], 1: [2, 3], 2: [4, 5], 3: [4, 5], 4: [6], 5: [], 6: []}) + C = [[0], [1], [2], [3], [4], [5], [6]] + cls.gc.append((G, C)) + + G = nx.DiGraph({0: [1], 1: [2, 3, 4], 2: [0, 3], 3: [4], 4: [3]}) + C = [[0, 1, 2], [3, 4]] + cls.gc.append((G, C)) + + def test_weakly_connected_components(self): + for G, C in self.gc: + U = G.to_undirected() + w = {frozenset(g) for g in nx.weakly_connected_components(G)} + c = {frozenset(g) for g in nx.connected_components(U)} + assert w == c + + def test_number_weakly_connected_components(self): + for G, C in self.gc: + U = G.to_undirected() + w = nx.number_weakly_connected_components(G) + c = nx.number_connected_components(U) + assert w == c + + def test_is_weakly_connected(self): + for G, C in self.gc: + U = G.to_undirected() + assert nx.is_weakly_connected(G) == nx.is_connected(U) + + def test_null_graph(self): + G = nx.DiGraph() + assert list(nx.weakly_connected_components(G)) == [] + assert nx.number_weakly_connected_components(G) == 0 + with pytest.raises(nx.NetworkXPointlessConcept): + next(nx.is_weakly_connected(G)) + + def test_connected_raise(self): + G = nx.Graph() + with pytest.raises(NetworkXNotImplemented): + next(nx.weakly_connected_components(G)) + pytest.raises(NetworkXNotImplemented, nx.number_weakly_connected_components, G) + pytest.raises(NetworkXNotImplemented, nx.is_weakly_connected, G) + + def test_connected_mutability(self): + DG = nx.path_graph(5, create_using=nx.DiGraph) + G = nx.disjoint_union(DG, DG) + seen = set() + for component in nx.weakly_connected_components(G): + assert len(seen & component) == 0 + seen.update(component) + component.clear() + + +def test_is_weakly_connected_empty_graph_raises(): + G = nx.DiGraph() + with pytest.raises(nx.NetworkXPointlessConcept, match="Connectivity is undefined"): + nx.is_weakly_connected(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d66b45523076bf6803d37e1201f2d96d723649f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b62de9a57dfaf90cb3a1cd0224e3dde6e4c5a9f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5623c9968c09eb128a8acc8faff72e2f6d69b3c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/cuts.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01b473b87201b422e53bbe7b1929ac51ab4e5085 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/disjoint_paths.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f5f90447b62bf9c13f33940f1085829da484c8d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_augmentation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52685a3286efd1e0bd1e6c537a3c7ff1b4182db6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/edge_kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba57f26e44f4b5e80a170ee13e611cef36efdcb3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe37b2d81be1704c9d5a9a3ccb0d16d8da88f2bb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/kcutsets.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f7fc68d38763e1c34582c9fa0a782df02db7a08 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/stoerwagner.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..42565392b2f24ba77b650a6fe7d1b317d7938b95 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/__pycache__/utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb4398a234fc6bff9d42fef5bd74d22a86a31c07 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_connectivity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_connectivity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1864cc86499ea2bc30928a59f069af267097e9c3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_connectivity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_cuts.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_cuts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0f22aed6301124f1edc454a38fb94f101d03f05 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_cuts.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_disjoint_paths.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_disjoint_paths.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9778fc447deafa9fc249274792fe111bc5aba01c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_disjoint_paths.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_augmentation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_augmentation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9da37649d6fefc8ef987f7226342623649e60ef8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_augmentation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b8700260772a892cccaefa2e44929fabbe4c8c3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_edge_kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcomponents.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcomponents.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..672a973e623facf5260ab600cbc57a8c80bb81dd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcomponents.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcutsets.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcutsets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c270ff4c87248151cf49e83b073c7941e65b015e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_kcutsets.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_stoer_wagner.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_stoer_wagner.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eced60af483db67d4d716767910bf7fc0fe3063a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/__pycache__/test_stoer_wagner.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_connectivity.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_connectivity.py new file mode 100644 index 0000000000000000000000000000000000000000..7aef2477d1331bcefc7e5dfdacd415b27ffcd3c8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_connectivity.py @@ -0,0 +1,421 @@ +import itertools + +import pytest + +import networkx as nx +from networkx.algorithms import flow +from networkx.algorithms.connectivity import ( + local_edge_connectivity, + local_node_connectivity, +) + +flow_funcs = [ + flow.boykov_kolmogorov, + flow.dinitz, + flow.edmonds_karp, + flow.preflow_push, + flow.shortest_augmenting_path, +] + + +# helper functions for tests + + +def _generate_no_biconnected(max_attempts=50): + attempts = 0 + while True: + G = nx.fast_gnp_random_graph(100, 0.0575, seed=42) + if nx.is_connected(G) and not nx.is_biconnected(G): + attempts = 0 + yield G + else: + if attempts >= max_attempts: + msg = f"Tried {max_attempts} times: no suitable Graph." + raise Exception(msg) + else: + attempts += 1 + + +def test_average_connectivity(): + # figure 1 from: + # Beineke, L., O. Oellermann, and R. Pippert (2002). The average + # connectivity of a graph. Discrete mathematics 252(1-3), 31-45 + # http://www.sciencedirect.com/science/article/pii/S0012365X01001807 + G1 = nx.path_graph(3) + G1.add_edges_from([(1, 3), (1, 4)]) + G2 = nx.path_graph(3) + G2.add_edges_from([(1, 3), (1, 4), (0, 3), (0, 4), (3, 4)]) + G3 = nx.Graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert nx.average_node_connectivity(G1, **kwargs) == 1, errmsg + assert nx.average_node_connectivity(G2, **kwargs) == 2.2, errmsg + assert nx.average_node_connectivity(G3, **kwargs) == 0, errmsg + + +def test_average_connectivity_directed(): + G = nx.DiGraph([(1, 3), (1, 4), (1, 5)]) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert nx.average_node_connectivity(G) == 0.25, errmsg + + +def test_articulation_points(): + Ggen = _generate_no_biconnected() + for flow_func in flow_funcs: + for i in range(3): + G = next(Ggen) + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert nx.node_connectivity(G, flow_func=flow_func) == 1, errmsg + + +def test_brandes_erlebach(): + # Figure 1 chapter 7: Connectivity + # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + G = nx.Graph() + G.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 6), + (3, 4), + (3, 6), + (4, 6), + (4, 7), + (5, 7), + (6, 8), + (6, 9), + (7, 8), + (7, 10), + (8, 11), + (9, 10), + (9, 11), + (10, 11), + ] + ) + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 3 == local_edge_connectivity(G, 1, 11, **kwargs), errmsg + assert 3 == nx.edge_connectivity(G, 1, 11, **kwargs), errmsg + assert 2 == local_node_connectivity(G, 1, 11, **kwargs), errmsg + assert 2 == nx.node_connectivity(G, 1, 11, **kwargs), errmsg + assert 2 == nx.edge_connectivity(G, **kwargs), errmsg + assert 2 == nx.node_connectivity(G, **kwargs), errmsg + if flow_func is flow.preflow_push: + assert 3 == nx.edge_connectivity(G, 1, 11, cutoff=2, **kwargs), errmsg + else: + assert 2 == nx.edge_connectivity(G, 1, 11, cutoff=2, **kwargs), errmsg + + +def test_white_harary_1(): + # Figure 1b white and harary (2001) + # https://doi.org/10.1111/0081-1750.00098 + # A graph with high adhesion (edge connectivity) and low cohesion + # (vertex connectivity) + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.remove_node(7) + for i in range(4, 7): + G.add_edge(0, i) + G = nx.disjoint_union(G, nx.complete_graph(4)) + G.remove_node(G.order() - 1) + for i in range(7, 10): + G.add_edge(0, i) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_white_harary_2(): + # Figure 8 white and harary (2001) + # https://doi.org/10.1111/0081-1750.00098 + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.add_edge(0, 4) + # kappa <= lambda <= delta + assert 3 == min(nx.core_number(G).values()) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_complete_graphs(): + for n in range(5, 20, 5): + for flow_func in flow_funcs: + G = nx.complete_graph(n) + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert n - 1 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert n - 1 == nx.node_connectivity( + G.to_directed(), flow_func=flow_func + ), errmsg + assert n - 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + assert n - 1 == nx.edge_connectivity( + G.to_directed(), flow_func=flow_func + ), errmsg + + +def test_empty_graphs(): + for k in range(5, 25, 5): + G = nx.empty_graph(k) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 0 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 0 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_petersen(): + G = nx.petersen_graph() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_tutte(): + G = nx.tutte_graph() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_dodecahedral(): + G = nx.dodecahedral_graph() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 3 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 3 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_octahedral(): + G = nx.octahedral_graph() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 4 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 4 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_icosahedral(): + G = nx.icosahedral_graph() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 5 == nx.node_connectivity(G, flow_func=flow_func), errmsg + assert 5 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + + +def test_missing_source(): + G = nx.path_graph(4) + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, nx.node_connectivity, G, 10, 1, flow_func=flow_func + ) + + +def test_missing_target(): + G = nx.path_graph(4) + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, nx.node_connectivity, G, 1, 10, flow_func=flow_func + ) + + +def test_edge_missing_source(): + G = nx.path_graph(4) + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, nx.edge_connectivity, G, 10, 1, flow_func=flow_func + ) + + +def test_edge_missing_target(): + G = nx.path_graph(4) + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, nx.edge_connectivity, G, 1, 10, flow_func=flow_func + ) + + +def test_not_weakly_connected(): + G = nx.DiGraph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert nx.node_connectivity(G) == 0, errmsg + assert nx.edge_connectivity(G) == 0, errmsg + + +def test_not_connected(): + G = nx.Graph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert nx.node_connectivity(G) == 0, errmsg + assert nx.edge_connectivity(G) == 0, errmsg + + +def test_directed_edge_connectivity(): + G = nx.cycle_graph(10, create_using=nx.DiGraph()) # only one direction + D = nx.cycle_graph(10).to_directed() # 2 reciprocal edges + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert 1 == nx.edge_connectivity(G, flow_func=flow_func), errmsg + assert 1 == local_edge_connectivity(G, 1, 4, flow_func=flow_func), errmsg + assert 1 == nx.edge_connectivity(G, 1, 4, flow_func=flow_func), errmsg + assert 2 == nx.edge_connectivity(D, flow_func=flow_func), errmsg + assert 2 == local_edge_connectivity(D, 1, 4, flow_func=flow_func), errmsg + assert 2 == nx.edge_connectivity(D, 1, 4, flow_func=flow_func), errmsg + + +def test_cutoff(): + G = nx.complete_graph(5) + for local_func in [local_edge_connectivity, local_node_connectivity]: + for flow_func in flow_funcs: + if flow_func is flow.preflow_push: + # cutoff is not supported by preflow_push + continue + for cutoff in [3, 2, 1]: + result = local_func(G, 0, 4, flow_func=flow_func, cutoff=cutoff) + assert cutoff == result, f"cutoff error in {flow_func.__name__}" + + +def test_invalid_auxiliary(): + G = nx.complete_graph(5) + pytest.raises(nx.NetworkXError, local_node_connectivity, G, 0, 3, auxiliary=G) + + +def test_interface_only_source(): + G = nx.complete_graph(5) + for interface_func in [nx.node_connectivity, nx.edge_connectivity]: + pytest.raises(nx.NetworkXError, interface_func, G, s=0) + + +def test_interface_only_target(): + G = nx.complete_graph(5) + for interface_func in [nx.node_connectivity, nx.edge_connectivity]: + pytest.raises(nx.NetworkXError, interface_func, G, t=3) + + +def test_edge_connectivity_flow_vs_stoer_wagner(): + graph_funcs = [nx.icosahedral_graph, nx.octahedral_graph, nx.dodecahedral_graph] + for graph_func in graph_funcs: + G = graph_func() + assert nx.stoer_wagner(G)[0] == nx.edge_connectivity(G) + + +class TestAllPairsNodeConnectivity: + @classmethod + def setup_class(cls): + cls.path = nx.path_graph(7) + cls.directed_path = nx.path_graph(7, create_using=nx.DiGraph()) + cls.cycle = nx.cycle_graph(7) + cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) + cls.gnp = nx.gnp_random_graph(30, 0.1, seed=42) + cls.directed_gnp = nx.gnp_random_graph(30, 0.1, directed=True, seed=42) + cls.K20 = nx.complete_graph(20) + cls.K10 = nx.complete_graph(10) + cls.K5 = nx.complete_graph(5) + cls.G_list = [ + cls.path, + cls.directed_path, + cls.cycle, + cls.directed_cycle, + cls.gnp, + cls.directed_gnp, + cls.K10, + cls.K5, + cls.K20, + ] + + def test_cycles(self): + K_undir = nx.all_pairs_node_connectivity(self.cycle) + for source in K_undir: + for target, k in K_undir[source].items(): + assert k == 2 + K_dir = nx.all_pairs_node_connectivity(self.directed_cycle) + for source in K_dir: + for target, k in K_dir[source].items(): + assert k == 1 + + def test_complete(self): + for G in [self.K10, self.K5, self.K20]: + K = nx.all_pairs_node_connectivity(G) + for source in K: + for target, k in K[source].items(): + assert k == len(G) - 1 + + def test_paths(self): + K_undir = nx.all_pairs_node_connectivity(self.path) + for source in K_undir: + for target, k in K_undir[source].items(): + assert k == 1 + K_dir = nx.all_pairs_node_connectivity(self.directed_path) + for source in K_dir: + for target, k in K_dir[source].items(): + if source < target: + assert k == 1 + else: + assert k == 0 + + def test_all_pairs_connectivity_nbunch(self): + G = nx.complete_graph(5) + nbunch = [0, 2, 3] + C = nx.all_pairs_node_connectivity(G, nbunch=nbunch) + assert len(C) == len(nbunch) + + def test_all_pairs_connectivity_icosahedral(self): + G = nx.icosahedral_graph() + C = nx.all_pairs_node_connectivity(G) + assert all(5 == C[u][v] for u, v in itertools.combinations(G, 2)) + + def test_all_pairs_connectivity(self): + G = nx.Graph() + nodes = [0, 1, 2, 3] + nx.add_path(G, nodes) + A = {n: {} for n in G} + for u, v in itertools.combinations(nodes, 2): + A[u][v] = A[v][u] = nx.node_connectivity(G, u, v) + C = nx.all_pairs_node_connectivity(G) + assert sorted((k, sorted(v)) for k, v in A.items()) == sorted( + (k, sorted(v)) for k, v in C.items() + ) + + def test_all_pairs_connectivity_directed(self): + G = nx.DiGraph() + nodes = [0, 1, 2, 3] + nx.add_path(G, nodes) + A = {n: {} for n in G} + for u, v in itertools.permutations(nodes, 2): + A[u][v] = nx.node_connectivity(G, u, v) + C = nx.all_pairs_node_connectivity(G) + assert sorted((k, sorted(v)) for k, v in A.items()) == sorted( + (k, sorted(v)) for k, v in C.items() + ) + + def test_all_pairs_connectivity_nbunch_combinations(self): + G = nx.complete_graph(5) + nbunch = [0, 2, 3] + A = {n: {} for n in nbunch} + for u, v in itertools.combinations(nbunch, 2): + A[u][v] = A[v][u] = nx.node_connectivity(G, u, v) + C = nx.all_pairs_node_connectivity(G, nbunch=nbunch) + assert sorted((k, sorted(v)) for k, v in A.items()) == sorted( + (k, sorted(v)) for k, v in C.items() + ) + + def test_all_pairs_connectivity_nbunch_iter(self): + G = nx.complete_graph(5) + nbunch = [0, 2, 3] + A = {n: {} for n in nbunch} + for u, v in itertools.combinations(nbunch, 2): + A[u][v] = A[v][u] = nx.node_connectivity(G, u, v) + C = nx.all_pairs_node_connectivity(G, nbunch=iter(nbunch)) + assert sorted((k, sorted(v)) for k, v in A.items()) == sorted( + (k, sorted(v)) for k, v in C.items() + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_cuts.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_cuts.py new file mode 100644 index 0000000000000000000000000000000000000000..964aff9c5632eb81fe5e5a8f22b1a88de73326e5 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_cuts.py @@ -0,0 +1,309 @@ +import pytest + +import networkx as nx +from networkx.algorithms import flow +from networkx.algorithms.connectivity import minimum_st_edge_cut, minimum_st_node_cut +from networkx.utils import arbitrary_element + +flow_funcs = [ + flow.boykov_kolmogorov, + flow.dinitz, + flow.edmonds_karp, + flow.preflow_push, + flow.shortest_augmenting_path, +] + +# Tests for node and edge cutsets + + +def _generate_no_biconnected(max_attempts=50): + attempts = 0 + while True: + G = nx.fast_gnp_random_graph(100, 0.0575, seed=42) + if nx.is_connected(G) and not nx.is_biconnected(G): + attempts = 0 + yield G + else: + if attempts >= max_attempts: + msg = f"Tried {attempts} times: no suitable Graph." + raise Exception(msg) + else: + attempts += 1 + + +def test_articulation_points(): + Ggen = _generate_no_biconnected() + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + for i in range(1): # change 1 to 3 or more for more realizations. + G = next(Ggen) + cut = nx.minimum_node_cut(G, flow_func=flow_func) + assert len(cut) == 1, errmsg + assert cut.pop() in set(nx.articulation_points(G)), errmsg + + +def test_brandes_erlebach_book(): + # Figure 1 chapter 7: Connectivity + # http://www.informatik.uni-augsburg.de/thi/personen/kammer/Graph_Connectivity.pdf + G = nx.Graph() + G.add_edges_from( + [ + (1, 2), + (1, 3), + (1, 4), + (1, 5), + (2, 3), + (2, 6), + (3, 4), + (3, 6), + (4, 6), + (4, 7), + (5, 7), + (6, 8), + (6, 9), + (7, 8), + (7, 10), + (8, 11), + (9, 10), + (9, 11), + (10, 11), + ] + ) + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge cutsets + assert 3 == len(nx.minimum_edge_cut(G, 1, 11, **kwargs)), errmsg + edge_cut = nx.minimum_edge_cut(G, **kwargs) + # Node 5 has only two edges + assert 2 == len(edge_cut), errmsg + H = G.copy() + H.remove_edges_from(edge_cut) + assert not nx.is_connected(H), errmsg + # node cuts + assert {6, 7} == minimum_st_node_cut(G, 1, 11, **kwargs), errmsg + assert {6, 7} == nx.minimum_node_cut(G, 1, 11, **kwargs), errmsg + node_cut = nx.minimum_node_cut(G, **kwargs) + assert 2 == len(node_cut), errmsg + H = G.copy() + H.remove_nodes_from(node_cut) + assert not nx.is_connected(H), errmsg + + +def test_white_harary_paper(): + # Figure 1b white and harary (2001) + # https://doi.org/10.1111/0081-1750.00098 + # A graph with high adhesion (edge connectivity) and low cohesion + # (node connectivity) + G = nx.disjoint_union(nx.complete_graph(4), nx.complete_graph(4)) + G.remove_node(7) + for i in range(4, 7): + G.add_edge(0, i) + G = nx.disjoint_union(G, nx.complete_graph(4)) + G.remove_node(G.order() - 1) + for i in range(7, 10): + G.add_edge(0, i) + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge cuts + edge_cut = nx.minimum_edge_cut(G, **kwargs) + assert 3 == len(edge_cut), errmsg + H = G.copy() + H.remove_edges_from(edge_cut) + assert not nx.is_connected(H), errmsg + # node cuts + node_cut = nx.minimum_node_cut(G, **kwargs) + assert {0} == node_cut, errmsg + H = G.copy() + H.remove_nodes_from(node_cut) + assert not nx.is_connected(H), errmsg + + +def test_petersen_cutset(): + G = nx.petersen_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge cuts + edge_cut = nx.minimum_edge_cut(G, **kwargs) + assert 3 == len(edge_cut), errmsg + H = G.copy() + H.remove_edges_from(edge_cut) + assert not nx.is_connected(H), errmsg + # node cuts + node_cut = nx.minimum_node_cut(G, **kwargs) + assert 3 == len(node_cut), errmsg + H = G.copy() + H.remove_nodes_from(node_cut) + assert not nx.is_connected(H), errmsg + + +def test_octahedral_cutset(): + G = nx.octahedral_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge cuts + edge_cut = nx.minimum_edge_cut(G, **kwargs) + assert 4 == len(edge_cut), errmsg + H = G.copy() + H.remove_edges_from(edge_cut) + assert not nx.is_connected(H), errmsg + # node cuts + node_cut = nx.minimum_node_cut(G, **kwargs) + assert 4 == len(node_cut), errmsg + H = G.copy() + H.remove_nodes_from(node_cut) + assert not nx.is_connected(H), errmsg + + +def test_icosahedral_cutset(): + G = nx.icosahedral_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge cuts + edge_cut = nx.minimum_edge_cut(G, **kwargs) + assert 5 == len(edge_cut), errmsg + H = G.copy() + H.remove_edges_from(edge_cut) + assert not nx.is_connected(H), errmsg + # node cuts + node_cut = nx.minimum_node_cut(G, **kwargs) + assert 5 == len(node_cut), errmsg + H = G.copy() + H.remove_nodes_from(node_cut) + assert not nx.is_connected(H), errmsg + + +def test_node_cutset_exception(): + G = nx.Graph() + G.add_edges_from([(1, 2), (3, 4)]) + for flow_func in flow_funcs: + pytest.raises(nx.NetworkXError, nx.minimum_node_cut, G, flow_func=flow_func) + + +def test_node_cutset_random_graphs(): + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + for i in range(3): + G = nx.fast_gnp_random_graph(50, 0.25, seed=42) + if not nx.is_connected(G): + ccs = iter(nx.connected_components(G)) + start = arbitrary_element(next(ccs)) + G.add_edges_from((start, arbitrary_element(c)) for c in ccs) + cutset = nx.minimum_node_cut(G, flow_func=flow_func) + assert nx.node_connectivity(G) == len(cutset), errmsg + G.remove_nodes_from(cutset) + assert not nx.is_connected(G), errmsg + + +def test_edge_cutset_random_graphs(): + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + for i in range(3): + G = nx.fast_gnp_random_graph(50, 0.25, seed=42) + if not nx.is_connected(G): + ccs = iter(nx.connected_components(G)) + start = arbitrary_element(next(ccs)) + G.add_edges_from((start, arbitrary_element(c)) for c in ccs) + cutset = nx.minimum_edge_cut(G, flow_func=flow_func) + assert nx.edge_connectivity(G) == len(cutset), errmsg + G.remove_edges_from(cutset) + assert not nx.is_connected(G), errmsg + + +def test_empty_graphs(): + G = nx.Graph() + D = nx.DiGraph() + for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]: + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXPointlessConcept, interface_func, G, flow_func=flow_func + ) + pytest.raises( + nx.NetworkXPointlessConcept, interface_func, D, flow_func=flow_func + ) + + +def test_unbounded(): + G = nx.complete_graph(5) + for flow_func in flow_funcs: + assert 4 == len(minimum_st_edge_cut(G, 1, 4, flow_func=flow_func)) + + +def test_missing_source(): + G = nx.path_graph(4) + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, interface_func, G, 10, 1, flow_func=flow_func + ) + + +def test_missing_target(): + G = nx.path_graph(4) + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + pytest.raises( + nx.NetworkXError, interface_func, G, 1, 10, flow_func=flow_func + ) + + +def test_not_weakly_connected(): + G = nx.DiGraph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + pytest.raises(nx.NetworkXError, interface_func, G, flow_func=flow_func) + + +def test_not_connected(): + G = nx.Graph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + pytest.raises(nx.NetworkXError, interface_func, G, flow_func=flow_func) + + +def tests_min_cut_complete(): + G = nx.complete_graph(5) + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + assert 4 == len(interface_func(G, flow_func=flow_func)) + + +def tests_min_cut_complete_directed(): + G = nx.complete_graph(5) + G = G.to_directed() + for interface_func in [nx.minimum_edge_cut, nx.minimum_node_cut]: + for flow_func in flow_funcs: + assert 4 == len(interface_func(G, flow_func=flow_func)) + + +def tests_minimum_st_node_cut(): + G = nx.Graph() + G.add_nodes_from([0, 1, 2, 3, 7, 8, 11, 12]) + G.add_edges_from([(7, 11), (1, 11), (1, 12), (12, 8), (0, 1)]) + nodelist = minimum_st_node_cut(G, 7, 11) + assert nodelist == set() + + +def test_invalid_auxiliary(): + G = nx.complete_graph(5) + pytest.raises(nx.NetworkXError, minimum_st_node_cut, G, 0, 3, auxiliary=G) + + +def test_interface_only_source(): + G = nx.complete_graph(5) + for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]: + pytest.raises(nx.NetworkXError, interface_func, G, s=0) + + +def test_interface_only_target(): + G = nx.complete_graph(5) + for interface_func in [nx.minimum_node_cut, nx.minimum_edge_cut]: + pytest.raises(nx.NetworkXError, interface_func, G, t=3) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_disjoint_paths.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_disjoint_paths.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0fad9f5ca474a6b547a399f8f284f7ff6e33a4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_disjoint_paths.py @@ -0,0 +1,249 @@ +import pytest + +import networkx as nx +from networkx.algorithms import flow +from networkx.utils import pairwise + +flow_funcs = [ + flow.boykov_kolmogorov, + flow.edmonds_karp, + flow.dinitz, + flow.preflow_push, + flow.shortest_augmenting_path, +] + + +def is_path(G, path): + return all(v in G[u] for u, v in pairwise(path)) + + +def are_edge_disjoint_paths(G, paths): + if not paths: + return False + for path in paths: + assert is_path(G, path) + paths_edges = [list(pairwise(p)) for p in paths] + num_of_edges = sum(len(e) for e in paths_edges) + num_unique_edges = len(set.union(*[set(es) for es in paths_edges])) + if num_of_edges == num_unique_edges: + return True + return False + + +def are_node_disjoint_paths(G, paths): + if not paths: + return False + for path in paths: + assert is_path(G, path) + # first and last nodes are source and target + st = {paths[0][0], paths[0][-1]} + num_of_nodes = len([n for path in paths for n in path if n not in st]) + num_unique_nodes = len({n for path in paths for n in path if n not in st}) + if num_of_nodes == num_unique_nodes: + return True + return False + + +def test_graph_from_pr_2053(): + G = nx.Graph() + G.add_edges_from( + [ + ("A", "B"), + ("A", "D"), + ("A", "F"), + ("A", "G"), + ("B", "C"), + ("B", "D"), + ("B", "G"), + ("C", "D"), + ("C", "E"), + ("C", "Z"), + ("D", "E"), + ("D", "F"), + ("E", "F"), + ("E", "Z"), + ("F", "Z"), + ("G", "Z"), + ] + ) + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_paths = list(nx.edge_disjoint_paths(G, "A", "Z", **kwargs)) + assert are_edge_disjoint_paths(G, edge_paths), errmsg + assert nx.edge_connectivity(G, "A", "Z") == len(edge_paths), errmsg + # node disjoint paths + node_paths = list(nx.node_disjoint_paths(G, "A", "Z", **kwargs)) + assert are_node_disjoint_paths(G, node_paths), errmsg + assert nx.node_connectivity(G, "A", "Z") == len(node_paths), errmsg + + +def test_florentine_families(): + G = nx.florentine_families_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, "Medici", "Strozzi", **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert nx.edge_connectivity(G, "Medici", "Strozzi") == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, "Medici", "Strozzi", **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert nx.node_connectivity(G, "Medici", "Strozzi") == len(node_dpaths), errmsg + + +def test_karate(): + G = nx.karate_club_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 33, **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert nx.edge_connectivity(G, 0, 33) == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, 0, 33, **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert nx.node_connectivity(G, 0, 33) == len(node_dpaths), errmsg + + +def test_petersen_disjoint_paths(): + G = nx.petersen_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert 3 == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert 3 == len(node_dpaths), errmsg + + +def test_octahedral_disjoint_paths(): + G = nx.octahedral_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 5, **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert 4 == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, 0, 5, **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert 4 == len(node_dpaths), errmsg + + +def test_icosahedral_disjoint_paths(): + G = nx.icosahedral_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert 5 == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert 5 == len(node_dpaths), errmsg + + +def test_cutoff_disjoint_paths(): + G = nx.icosahedral_graph() + for flow_func in flow_funcs: + kwargs = {"flow_func": flow_func} + errmsg = f"Assertion failed in function: {flow_func.__name__}" + for cutoff in [2, 4]: + kwargs["cutoff"] = cutoff + # edge disjoint paths + edge_dpaths = list(nx.edge_disjoint_paths(G, 0, 6, **kwargs)) + assert are_edge_disjoint_paths(G, edge_dpaths), errmsg + assert cutoff == len(edge_dpaths), errmsg + # node disjoint paths + node_dpaths = list(nx.node_disjoint_paths(G, 0, 6, **kwargs)) + assert are_node_disjoint_paths(G, node_dpaths), errmsg + assert cutoff == len(node_dpaths), errmsg + + +def test_missing_source_edge_paths(): + with pytest.raises(nx.NetworkXError): + G = nx.path_graph(4) + list(nx.edge_disjoint_paths(G, 10, 1)) + + +def test_missing_source_node_paths(): + with pytest.raises(nx.NetworkXError): + G = nx.path_graph(4) + list(nx.node_disjoint_paths(G, 10, 1)) + + +def test_missing_target_edge_paths(): + with pytest.raises(nx.NetworkXError): + G = nx.path_graph(4) + list(nx.edge_disjoint_paths(G, 1, 10)) + + +def test_missing_target_node_paths(): + with pytest.raises(nx.NetworkXError): + G = nx.path_graph(4) + list(nx.node_disjoint_paths(G, 1, 10)) + + +def test_not_weakly_connected_edges(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.DiGraph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + list(nx.edge_disjoint_paths(G, 1, 5)) + + +def test_not_weakly_connected_nodes(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.DiGraph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + list(nx.node_disjoint_paths(G, 1, 5)) + + +def test_not_connected_edges(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.Graph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + list(nx.edge_disjoint_paths(G, 1, 5)) + + +def test_not_connected_nodes(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.Graph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5]) + list(nx.node_disjoint_paths(G, 1, 5)) + + +def test_isolated_edges(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.Graph() + G.add_node(1) + nx.add_path(G, [4, 5]) + list(nx.edge_disjoint_paths(G, 1, 5)) + + +def test_isolated_nodes(): + with pytest.raises(nx.NetworkXNoPath): + G = nx.Graph() + G.add_node(1) + nx.add_path(G, [4, 5]) + list(nx.node_disjoint_paths(G, 1, 5)) + + +def test_invalid_auxiliary(): + with pytest.raises(nx.NetworkXError): + G = nx.complete_graph(5) + list(nx.node_disjoint_paths(G, 0, 3, auxiliary=G)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_augmentation.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_augmentation.py new file mode 100644 index 0000000000000000000000000000000000000000..03e7c92441f516d7ac001707cbf4f23bf89f8f1d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_augmentation.py @@ -0,0 +1,502 @@ +import itertools as it +import random + +import pytest + +import networkx as nx +from networkx.algorithms.connectivity import k_edge_augmentation +from networkx.algorithms.connectivity.edge_augmentation import ( + _unpack_available_edges, + collapse, + complement_edges, + is_k_edge_connected, + is_locally_k_edge_connected, +) +from networkx.utils import pairwise + +# This should be set to the largest k for which an efficient algorithm is +# explicitly defined. +MAX_EFFICIENT_K = 2 + + +def tarjan_bridge_graph(): + # graph from tarjan paper + # RE Tarjan - "A note on finding the bridges of a graph" + # Information Processing Letters, 1974 - Elsevier + # doi:10.1016/0020-0190(74)90003-9. + # define 2-connected components and bridges + ccs = [ + (1, 2, 4, 3, 1, 4), + (5, 6, 7, 5), + (8, 9, 10, 8), + (17, 18, 16, 15, 17), + (11, 12, 14, 13, 11, 14), + ] + bridges = [(4, 8), (3, 5), (3, 17)] + G = nx.Graph(it.chain(*(pairwise(path) for path in ccs + bridges))) + return G + + +def test_weight_key(): + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4, 5, 6, 7, 8, 9]) + G.add_edges_from([(3, 8), (1, 2), (2, 3)]) + impossible = {(3, 6), (3, 9)} + rng = random.Random(0) + avail_uv = list(set(complement_edges(G)) - impossible) + avail = [(u, v, {"cost": rng.random()}) for u, v in avail_uv] + + _augment_and_check(G, k=1) + _augment_and_check(G, k=1, avail=avail_uv) + _augment_and_check(G, k=1, avail=avail, weight="cost") + + _check_augmentations(G, avail, weight="cost") + + +def test_is_locally_k_edge_connected_exceptions(): + pytest.raises(nx.NetworkXNotImplemented, is_k_edge_connected, nx.DiGraph(), k=0) + pytest.raises(nx.NetworkXNotImplemented, is_k_edge_connected, nx.MultiGraph(), k=0) + pytest.raises(ValueError, is_k_edge_connected, nx.Graph(), k=0) + + +def test_is_k_edge_connected(): + G = nx.barbell_graph(10, 0) + assert is_k_edge_connected(G, k=1) + assert not is_k_edge_connected(G, k=2) + + G = nx.Graph() + G.add_nodes_from([5, 15]) + assert not is_k_edge_connected(G, k=1) + assert not is_k_edge_connected(G, k=2) + + G = nx.complete_graph(5) + assert is_k_edge_connected(G, k=1) + assert is_k_edge_connected(G, k=2) + assert is_k_edge_connected(G, k=3) + assert is_k_edge_connected(G, k=4) + + G = nx.compose(nx.complete_graph([0, 1, 2]), nx.complete_graph([3, 4, 5])) + assert not is_k_edge_connected(G, k=1) + assert not is_k_edge_connected(G, k=2) + assert not is_k_edge_connected(G, k=3) + + +def test_is_k_edge_connected_exceptions(): + pytest.raises( + nx.NetworkXNotImplemented, is_locally_k_edge_connected, nx.DiGraph(), 1, 2, k=0 + ) + pytest.raises( + nx.NetworkXNotImplemented, + is_locally_k_edge_connected, + nx.MultiGraph(), + 1, + 2, + k=0, + ) + pytest.raises(ValueError, is_locally_k_edge_connected, nx.Graph(), 1, 2, k=0) + + +def test_is_locally_k_edge_connected(): + G = nx.barbell_graph(10, 0) + assert is_locally_k_edge_connected(G, 5, 15, k=1) + assert not is_locally_k_edge_connected(G, 5, 15, k=2) + + G = nx.Graph() + G.add_nodes_from([5, 15]) + assert not is_locally_k_edge_connected(G, 5, 15, k=2) + + +def test_null_graph(): + G = nx.Graph() + _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2) + + +def test_cliques(): + for n in range(1, 10): + G = nx.complete_graph(n) + _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2) + + +def test_clique_and_node(): + for n in range(1, 10): + G = nx.complete_graph(n) + G.add_node(n + 1) + _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2) + + +def test_point_graph(): + G = nx.Graph() + G.add_node(1) + _check_augmentations(G, max_k=MAX_EFFICIENT_K + 2) + + +def test_edgeless_graph(): + G = nx.Graph() + G.add_nodes_from([1, 2, 3, 4]) + _check_augmentations(G) + + +def test_invalid_k(): + G = nx.Graph() + pytest.raises(ValueError, list, k_edge_augmentation(G, k=-1)) + pytest.raises(ValueError, list, k_edge_augmentation(G, k=0)) + + +def test_unfeasible(): + G = tarjan_bridge_graph() + pytest.raises(nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=1, avail=[])) + + pytest.raises(nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=2, avail=[])) + + pytest.raises( + nx.NetworkXUnfeasible, list, k_edge_augmentation(G, k=2, avail=[(7, 9)]) + ) + + # partial solutions should not error if real solutions are infeasible + aug_edges = list(k_edge_augmentation(G, k=2, avail=[(7, 9)], partial=True)) + assert aug_edges == [(7, 9)] + + _check_augmentations(G, avail=[], max_k=MAX_EFFICIENT_K + 2) + + _check_augmentations(G, avail=[(7, 9)], max_k=MAX_EFFICIENT_K + 2) + + +def test_tarjan(): + G = tarjan_bridge_graph() + + aug_edges = set(_augment_and_check(G, k=2)[0]) + print(f"aug_edges = {aug_edges!r}") + # can't assert edge exactly equality due to non-determinant edge order + # but we do know the size of the solution must be 3 + assert len(aug_edges) == 3 + + avail = [ + (9, 7), + (8, 5), + (2, 10), + (6, 13), + (11, 18), + (1, 17), + (2, 3), + (16, 17), + (18, 14), + (15, 14), + ] + aug_edges = set(_augment_and_check(G, avail=avail, k=2)[0]) + + # Can't assert exact length since approximation depends on the order of a + # dict traversal. + assert len(aug_edges) <= 3 * 2 + + _check_augmentations(G, avail) + + +def test_configuration(): + # seeds = [2718183590, 2470619828, 1694705158, 3001036531, 2401251497] + seeds = [1001, 1002, 1003, 1004] + for seed in seeds: + deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000) + G = nx.Graph(nx.configuration_model(deg_seq, seed=seed)) + G.remove_edges_from(nx.selfloop_edges(G)) + _check_augmentations(G) + + +def test_shell(): + # seeds = [2057382236, 3331169846, 1840105863, 476020778, 2247498425] + seeds = [18] + for seed in seeds: + constructor = [(12, 70, 0.8), (15, 40, 0.6)] + G = nx.random_shell_graph(constructor, seed=seed) + _check_augmentations(G) + + +def test_karate(): + G = nx.karate_club_graph() + _check_augmentations(G) + + +def test_star(): + G = nx.star_graph(3) + _check_augmentations(G) + + G = nx.star_graph(5) + _check_augmentations(G) + + G = nx.star_graph(10) + _check_augmentations(G) + + +def test_barbell(): + G = nx.barbell_graph(5, 0) + _check_augmentations(G) + + G = nx.barbell_graph(5, 2) + _check_augmentations(G) + + G = nx.barbell_graph(5, 3) + _check_augmentations(G) + + G = nx.barbell_graph(5, 4) + _check_augmentations(G) + + +def test_bridge(): + G = nx.Graph([(2393, 2257), (2393, 2685), (2685, 2257), (1758, 2257)]) + _check_augmentations(G) + + +def test_gnp_augmentation(): + rng = random.Random(0) + G = nx.gnp_random_graph(30, 0.005, seed=0) + # Randomly make edges available + avail = { + (u, v): 1 + rng.random() for u, v in complement_edges(G) if rng.random() < 0.25 + } + _check_augmentations(G, avail) + + +def _assert_solution_properties(G, aug_edges, avail_dict=None): + """Checks that aug_edges are consistently formatted""" + if avail_dict is not None: + assert all(e in avail_dict for e in aug_edges), ( + "when avail is specified aug-edges should be in avail" + ) + + unique_aug = set(map(tuple, map(sorted, aug_edges))) + unique_aug = list(map(tuple, map(sorted, aug_edges))) + assert len(aug_edges) == len(unique_aug), "edges should be unique" + + assert not any(u == v for u, v in unique_aug), "should be no self-edges" + + assert not any(G.has_edge(u, v) for u, v in unique_aug), ( + "aug edges and G.edges should be disjoint" + ) + + +def _augment_and_check( + G, k, avail=None, weight=None, verbose=False, orig_k=None, max_aug_k=None +): + """ + Does one specific augmentation and checks for properties of the result + """ + if orig_k is None: + try: + orig_k = nx.edge_connectivity(G) + except nx.NetworkXPointlessConcept: + orig_k = 0 + info = {} + try: + if avail is not None: + # ensure avail is in dict form + avail_dict = dict(zip(*_unpack_available_edges(avail, weight=weight))) + else: + avail_dict = None + try: + # Find the augmentation if possible + generator = nx.k_edge_augmentation(G, k=k, weight=weight, avail=avail) + assert not isinstance(generator, list), "should always return an iter" + aug_edges = [] + for edge in generator: + aug_edges.append(edge) + except nx.NetworkXUnfeasible: + infeasible = True + info["infeasible"] = True + assert len(aug_edges) == 0, "should not generate anything if unfeasible" + + if avail is None: + n_nodes = G.number_of_nodes() + assert n_nodes <= k, ( + "unconstrained cases are only unfeasible if |V| <= k. " + f"Got |V|={n_nodes} and k={k}" + ) + else: + if max_aug_k is None: + G_aug_all = G.copy() + G_aug_all.add_edges_from(avail_dict.keys()) + try: + max_aug_k = nx.edge_connectivity(G_aug_all) + except nx.NetworkXPointlessConcept: + max_aug_k = 0 + + assert max_aug_k < k, ( + "avail should only be unfeasible if using all edges " + "does not achieve k-edge-connectivity" + ) + + # Test for a partial solution + partial_edges = list( + nx.k_edge_augmentation(G, k=k, weight=weight, partial=True, avail=avail) + ) + + info["n_partial_edges"] = len(partial_edges) + + if avail_dict is None: + assert set(partial_edges) == set(complement_edges(G)), ( + "unweighted partial solutions should be the complement" + ) + elif len(avail_dict) > 0: + H = G.copy() + + # Find the partial / full augmented connectivity + H.add_edges_from(partial_edges) + partial_conn = nx.edge_connectivity(H) + + H.add_edges_from(set(avail_dict.keys())) + full_conn = nx.edge_connectivity(H) + + # Full connectivity should be no better than our partial + # solution. + assert partial_conn == full_conn, ( + "adding more edges should not increase k-conn" + ) + + # Find the new edge-connectivity after adding the augmenting edges + aug_edges = partial_edges + else: + infeasible = False + + # Find the weight of the augmentation + num_edges = len(aug_edges) + if avail is not None: + total_weight = sum(avail_dict[e] for e in aug_edges) + else: + total_weight = num_edges + + info["total_weight"] = total_weight + info["num_edges"] = num_edges + + # Find the new edge-connectivity after adding the augmenting edges + G_aug = G.copy() + G_aug.add_edges_from(aug_edges) + try: + aug_k = nx.edge_connectivity(G_aug) + except nx.NetworkXPointlessConcept: + aug_k = 0 + info["aug_k"] = aug_k + + # Do checks + if not infeasible and orig_k < k: + assert info["aug_k"] >= k, f"connectivity should increase to k={k} or more" + + assert info["aug_k"] >= orig_k, "augmenting should never reduce connectivity" + + _assert_solution_properties(G, aug_edges, avail_dict) + + except Exception: + info["failed"] = True + print(f"edges = {list(G.edges())}") + print(f"nodes = {list(G.nodes())}") + print(f"aug_edges = {list(aug_edges)}") + print(f"info = {info}") + raise + else: + if verbose: + print(f"info = {info}") + + if infeasible: + aug_edges = None + return aug_edges, info + + +def _check_augmentations(G, avail=None, max_k=None, weight=None, verbose=False): + """Helper to check weighted/unweighted cases with multiple values of k""" + # Using all available edges, find the maximum edge-connectivity + try: + orig_k = nx.edge_connectivity(G) + except nx.NetworkXPointlessConcept: + orig_k = 0 + + if avail is not None: + all_aug_edges = _unpack_available_edges(avail, weight=weight)[0] + G_aug_all = G.copy() + G_aug_all.add_edges_from(all_aug_edges) + try: + max_aug_k = nx.edge_connectivity(G_aug_all) + except nx.NetworkXPointlessConcept: + max_aug_k = 0 + else: + max_aug_k = G.number_of_nodes() - 1 + + if max_k is None: + max_k = min(4, max_aug_k) + + avail_uniform = {e: 1 for e in complement_edges(G)} + + if verbose: + print("\n=== CHECK_AUGMENTATION ===") + print(f"G.number_of_nodes = {G.number_of_nodes()!r}") + print(f"G.number_of_edges = {G.number_of_edges()!r}") + print(f"max_k = {max_k!r}") + print(f"max_aug_k = {max_aug_k!r}") + print(f"orig_k = {orig_k!r}") + + # check augmentation for multiple values of k + for k in range(1, max_k + 1): + if verbose: + print("---------------") + print(f"Checking k = {k}") + + # Check the unweighted version + if verbose: + print("unweighted case") + aug_edges1, info1 = _augment_and_check(G, k=k, verbose=verbose, orig_k=orig_k) + + # Check that the weighted version with all available edges and uniform + # weights gives a similar solution to the unweighted case. + if verbose: + print("weighted uniform case") + aug_edges2, info2 = _augment_and_check( + G, + k=k, + avail=avail_uniform, + verbose=verbose, + orig_k=orig_k, + max_aug_k=G.number_of_nodes() - 1, + ) + + # Check the weighted version + if avail is not None: + if verbose: + print("weighted case") + aug_edges3, info3 = _augment_and_check( + G, + k=k, + avail=avail, + weight=weight, + verbose=verbose, + max_aug_k=max_aug_k, + orig_k=orig_k, + ) + + if aug_edges1 is not None: + # Check approximation ratios + if k == 1: + # when k=1, both solutions should be optimal + assert info2["total_weight"] == info1["total_weight"] + if k == 2: + # when k=2, the weighted version is an approximation + if orig_k == 0: + # the approximation ratio is 3 if G is not connected + assert info2["total_weight"] <= info1["total_weight"] * 3 + else: + # the approximation ratio is 2 if G is was connected + assert info2["total_weight"] <= info1["total_weight"] * 2 + _check_unconstrained_bridge_property(G, info1) + + +def _check_unconstrained_bridge_property(G, info1): + # Check Theorem 5 from Eswaran and Tarjan. (1975) Augmentation problems + import math + + bridge_ccs = list(nx.connectivity.bridge_components(G)) + # condense G into an forest C + C = collapse(G, bridge_ccs) + + p = len([n for n, d in C.degree() if d == 1]) # leafs + q = len([n for n, d in C.degree() if d == 0]) # isolated + if p + q > 1: + size_target = math.ceil(p / 2) + q + size_aug = info1["num_edges"] + assert size_aug == size_target, ( + "augmentation size is different from what theory predicts" + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py new file mode 100644 index 0000000000000000000000000000000000000000..f14ed6466fad54e730ee593eb7b6ed4f99dc0fa8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py @@ -0,0 +1,488 @@ +import itertools as it + +import pytest + +import networkx as nx +from networkx.algorithms.connectivity import EdgeComponentAuxGraph, bridge_components +from networkx.algorithms.connectivity.edge_kcomponents import general_k_edge_subgraphs +from networkx.utils import pairwise + +# ---------------- +# Helper functions +# ---------------- + + +def fset(list_of_sets): + """allows == to be used for list of sets""" + return set(map(frozenset, list_of_sets)) + + +def _assert_subgraph_edge_connectivity(G, ccs_subgraph, k): + """ + tests properties of k-edge-connected subgraphs + + the actual edge connectivity should be no less than k unless the cc is a + single node. + """ + for cc in ccs_subgraph: + C = G.subgraph(cc) + if len(cc) > 1: + connectivity = nx.edge_connectivity(C) + assert connectivity >= k + + +def _memo_connectivity(G, u, v, memo): + edge = (u, v) + if edge in memo: + return memo[edge] + if not G.is_directed(): + redge = (v, u) + if redge in memo: + return memo[redge] + memo[edge] = nx.edge_connectivity(G, *edge) + return memo[edge] + + +def _all_pairs_connectivity(G, cc, k, memo): + # Brute force check + for u, v in it.combinations(cc, 2): + # Use a memoization dict to save on computation + connectivity = _memo_connectivity(G, u, v, memo) + if G.is_directed(): + connectivity = min(connectivity, _memo_connectivity(G, v, u, memo)) + assert connectivity >= k + + +def _assert_local_cc_edge_connectivity(G, ccs_local, k, memo): + """ + tests properties of k-edge-connected components + + the local edge connectivity between each pair of nodes in the original + graph should be no less than k unless the cc is a single node. + """ + for cc in ccs_local: + if len(cc) > 1: + # Strategy for testing a bit faster: If the subgraph has high edge + # connectivity then it must have local connectivity + C = G.subgraph(cc) + connectivity = nx.edge_connectivity(C) + if connectivity < k: + # Otherwise do the brute force (with memoization) check + _all_pairs_connectivity(G, cc, k, memo) + + +# Helper function +def _check_edge_connectivity(G): + """ + Helper - generates all k-edge-components using the aux graph. Checks the + both local and subgraph edge connectivity of each cc. Also checks that + alternate methods of computing the k-edge-ccs generate the same result. + """ + # Construct the auxiliary graph that can be used to make each k-cc or k-sub + aux_graph = EdgeComponentAuxGraph.construct(G) + + # memoize the local connectivity in this graph + memo = {} + + for k in it.count(1): + # Test "local" k-edge-components and k-edge-subgraphs + ccs_local = fset(aux_graph.k_edge_components(k)) + ccs_subgraph = fset(aux_graph.k_edge_subgraphs(k)) + + # Check connectivity properties that should be guaranteed by the + # algorithms. + _assert_local_cc_edge_connectivity(G, ccs_local, k, memo) + _assert_subgraph_edge_connectivity(G, ccs_subgraph, k) + + if k == 1 or k == 2 and not G.is_directed(): + assert ccs_local == ccs_subgraph, ( + "Subgraphs and components should be the same when k == 1 or (k == 2 and not G.directed())" + ) + + if G.is_directed(): + # Test special case methods are the same as the aux graph + if k == 1: + alt_sccs = fset(nx.strongly_connected_components(G)) + assert alt_sccs == ccs_local, "k=1 failed alt" + assert alt_sccs == ccs_subgraph, "k=1 failed alt" + else: + # Test special case methods are the same as the aux graph + if k == 1: + alt_ccs = fset(nx.connected_components(G)) + assert alt_ccs == ccs_local, "k=1 failed alt" + assert alt_ccs == ccs_subgraph, "k=1 failed alt" + elif k == 2: + alt_bridge_ccs = fset(bridge_components(G)) + assert alt_bridge_ccs == ccs_local, "k=2 failed alt" + assert alt_bridge_ccs == ccs_subgraph, "k=2 failed alt" + # if new methods for k == 3 or k == 4 are implemented add them here + + # Check the general subgraph method works by itself + alt_subgraph_ccs = fset( + [set(C.nodes()) for C in general_k_edge_subgraphs(G, k=k)] + ) + assert alt_subgraph_ccs == ccs_subgraph, "alt subgraph method failed" + + # Stop once k is larger than all special case methods + # and we cannot break down ccs any further. + if k > 2 and all(len(cc) == 1 for cc in ccs_local): + break + + +# ---------------- +# Misc tests +# ---------------- + + +def test_zero_k_exception(): + G = nx.Graph() + # functions that return generators error immediately + pytest.raises(ValueError, nx.k_edge_components, G, k=0) + pytest.raises(ValueError, nx.k_edge_subgraphs, G, k=0) + + # actual generators only error when you get the first item + aux_graph = EdgeComponentAuxGraph.construct(G) + pytest.raises(ValueError, list, aux_graph.k_edge_components(k=0)) + pytest.raises(ValueError, list, aux_graph.k_edge_subgraphs(k=0)) + + pytest.raises(ValueError, list, general_k_edge_subgraphs(G, k=0)) + + +def test_empty_input(): + G = nx.Graph() + assert [] == list(nx.k_edge_components(G, k=5)) + assert [] == list(nx.k_edge_subgraphs(G, k=5)) + + G = nx.DiGraph() + assert [] == list(nx.k_edge_components(G, k=5)) + assert [] == list(nx.k_edge_subgraphs(G, k=5)) + + +def test_not_implemented(): + G = nx.MultiGraph() + pytest.raises(nx.NetworkXNotImplemented, EdgeComponentAuxGraph.construct, G) + pytest.raises(nx.NetworkXNotImplemented, nx.k_edge_components, G, k=2) + pytest.raises(nx.NetworkXNotImplemented, nx.k_edge_subgraphs, G, k=2) + with pytest.raises(nx.NetworkXNotImplemented): + next(bridge_components(G)) + with pytest.raises(nx.NetworkXNotImplemented): + next(bridge_components(nx.DiGraph())) + + +def test_general_k_edge_subgraph_quick_return(): + # tests quick return optimization + G = nx.Graph() + G.add_node(0) + subgraphs = list(general_k_edge_subgraphs(G, k=1)) + assert len(subgraphs) == 1 + for subgraph in subgraphs: + assert subgraph.number_of_nodes() == 1 + + G.add_node(1) + subgraphs = list(general_k_edge_subgraphs(G, k=1)) + assert len(subgraphs) == 2 + for subgraph in subgraphs: + assert subgraph.number_of_nodes() == 1 + + +# ---------------- +# Undirected tests +# ---------------- + + +def test_random_gnp(): + # seeds = [1550709854, 1309423156, 4208992358, 2785630813, 1915069929] + seeds = [12, 13] + + for seed in seeds: + G = nx.gnp_random_graph(20, 0.2, seed=seed) + _check_edge_connectivity(G) + + +def test_configuration(): + # seeds = [2718183590, 2470619828, 1694705158, 3001036531, 2401251497] + seeds = [14, 15] + for seed in seeds: + deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000) + G = nx.Graph(nx.configuration_model(deg_seq, seed=seed)) + G.remove_edges_from(nx.selfloop_edges(G)) + _check_edge_connectivity(G) + + +def test_shell(): + # seeds = [2057382236, 3331169846, 1840105863, 476020778, 2247498425] + seeds = [20] + for seed in seeds: + constructor = [(12, 70, 0.8), (15, 40, 0.6)] + G = nx.random_shell_graph(constructor, seed=seed) + _check_edge_connectivity(G) + + +def test_karate(): + G = nx.karate_club_graph() + _check_edge_connectivity(G) + + +def test_tarjan_bridge(): + # graph from tarjan paper + # RE Tarjan - "A note on finding the bridges of a graph" + # Information Processing Letters, 1974 - Elsevier + # doi:10.1016/0020-0190(74)90003-9. + # define 2-connected components and bridges + ccs = [ + (1, 2, 4, 3, 1, 4), + (5, 6, 7, 5), + (8, 9, 10, 8), + (17, 18, 16, 15, 17), + (11, 12, 14, 13, 11, 14), + ] + bridges = [(4, 8), (3, 5), (3, 17)] + G = nx.Graph(it.chain(*(pairwise(path) for path in ccs + bridges))) + _check_edge_connectivity(G) + + +def test_bridge_cc(): + # define 2-connected components and bridges + cc2 = [(1, 2, 4, 3, 1, 4), (8, 9, 10, 8), (11, 12, 13, 11)] + bridges = [(4, 8), (3, 5), (20, 21), (22, 23, 24)] + G = nx.Graph(it.chain(*(pairwise(path) for path in cc2 + bridges))) + bridge_ccs = fset(bridge_components(G)) + target_ccs = fset( + [{1, 2, 3, 4}, {5}, {8, 9, 10}, {11, 12, 13}, {20}, {21}, {22}, {23}, {24}] + ) + assert bridge_ccs == target_ccs + _check_edge_connectivity(G) + + +def test_undirected_aux_graph(): + # Graph similar to the one in + # http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264 + a, b, c, d, e, f, g, h, i = "abcdefghi" + paths = [ + (a, d, b, f, c), + (a, e, b), + (a, e, b, c, g, b, a), + (c, b), + (f, g, f), + (h, i), + ] + G = nx.Graph(it.chain(*[pairwise(path) for path in paths])) + aux_graph = EdgeComponentAuxGraph.construct(G) + + components_1 = fset(aux_graph.k_edge_subgraphs(k=1)) + target_1 = fset([{a, b, c, d, e, f, g}, {h, i}]) + assert target_1 == components_1 + + # Check that the undirected case for k=1 agrees with CCs + alt_1 = fset(nx.k_edge_subgraphs(G, k=1)) + assert alt_1 == components_1 + + components_2 = fset(aux_graph.k_edge_subgraphs(k=2)) + target_2 = fset([{a, b, c, d, e, f, g}, {h}, {i}]) + assert target_2 == components_2 + + # Check that the undirected case for k=2 agrees with bridge components + alt_2 = fset(nx.k_edge_subgraphs(G, k=2)) + assert alt_2 == components_2 + + components_3 = fset(aux_graph.k_edge_subgraphs(k=3)) + target_3 = fset([{a}, {b, c, f, g}, {d}, {e}, {h}, {i}]) + assert target_3 == components_3 + + components_4 = fset(aux_graph.k_edge_subgraphs(k=4)) + target_4 = fset([{a}, {b}, {c}, {d}, {e}, {f}, {g}, {h}, {i}]) + assert target_4 == components_4 + + _check_edge_connectivity(G) + + +def test_local_subgraph_difference(): + paths = [ + (11, 12, 13, 14, 11, 13, 14, 12), # first 4-clique + (21, 22, 23, 24, 21, 23, 24, 22), # second 4-clique + # paths connecting each node of the 4 cliques + (11, 101, 21), + (12, 102, 22), + (13, 103, 23), + (14, 104, 24), + ] + G = nx.Graph(it.chain(*[pairwise(path) for path in paths])) + aux_graph = EdgeComponentAuxGraph.construct(G) + + # Each clique is returned separately in k-edge-subgraphs + subgraph_ccs = fset(aux_graph.k_edge_subgraphs(3)) + subgraph_target = fset( + [{101}, {102}, {103}, {104}, {21, 22, 23, 24}, {11, 12, 13, 14}] + ) + assert subgraph_ccs == subgraph_target + + # But in k-edge-ccs they are returned together + # because they are locally 3-edge-connected + local_ccs = fset(aux_graph.k_edge_components(3)) + local_target = fset([{101}, {102}, {103}, {104}, {11, 12, 13, 14, 21, 22, 23, 24}]) + assert local_ccs == local_target + + +def test_local_subgraph_difference_directed(): + dipaths = [(1, 2, 3, 4, 1), (1, 3, 1)] + G = nx.DiGraph(it.chain(*[pairwise(path) for path in dipaths])) + + assert fset(nx.k_edge_components(G, k=1)) == fset(nx.k_edge_subgraphs(G, k=1)) + + # Unlike undirected graphs, when k=2, for directed graphs there is a case + # where the k-edge-ccs are not the same as the k-edge-subgraphs. + # (in directed graphs ccs and subgraphs are the same when k=2) + assert fset(nx.k_edge_components(G, k=2)) != fset(nx.k_edge_subgraphs(G, k=2)) + + assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3)) + + _check_edge_connectivity(G) + + +def test_triangles(): + paths = [ + (11, 12, 13, 11), # first 3-clique + (21, 22, 23, 21), # second 3-clique + (11, 21), # connected by an edge + ] + G = nx.Graph(it.chain(*[pairwise(path) for path in paths])) + + # subgraph and ccs are the same in all cases here + assert fset(nx.k_edge_components(G, k=1)) == fset(nx.k_edge_subgraphs(G, k=1)) + + assert fset(nx.k_edge_components(G, k=2)) == fset(nx.k_edge_subgraphs(G, k=2)) + + assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3)) + + _check_edge_connectivity(G) + + +def test_four_clique(): + paths = [ + (11, 12, 13, 14, 11, 13, 14, 12), # first 4-clique + (21, 22, 23, 24, 21, 23, 24, 22), # second 4-clique + # paths connecting the 4 cliques such that they are + # 3-connected in G, but not in the subgraph. + # Case where the nodes bridging them do not have degree less than 3. + (100, 13), + (12, 100, 22), + (13, 200, 23), + (14, 300, 24), + ] + G = nx.Graph(it.chain(*[pairwise(path) for path in paths])) + + # The subgraphs and ccs are different for k=3 + local_ccs = fset(nx.k_edge_components(G, k=3)) + subgraphs = fset(nx.k_edge_subgraphs(G, k=3)) + assert local_ccs != subgraphs + + # The cliques ares in the same cc + clique1 = frozenset(paths[0]) + clique2 = frozenset(paths[1]) + assert clique1.union(clique2).union({100}) in local_ccs + + # but different subgraphs + assert clique1 in subgraphs + assert clique2 in subgraphs + + assert G.degree(100) == 3 + + _check_edge_connectivity(G) + + +def test_five_clique(): + # Make a graph that can be disconnected less than 4 edges, but no node has + # degree less than 4. + G = nx.disjoint_union(nx.complete_graph(5), nx.complete_graph(5)) + paths = [ + # add aux-connections + (1, 100, 6), + (2, 100, 7), + (3, 200, 8), + (4, 200, 100), + ] + G.add_edges_from(it.chain(*[pairwise(path) for path in paths])) + assert min(dict(nx.degree(G)).values()) == 4 + + # For k=3 they are the same + assert fset(nx.k_edge_components(G, k=3)) == fset(nx.k_edge_subgraphs(G, k=3)) + + # For k=4 they are the different + # the aux nodes are in the same CC as clique 1 but no the same subgraph + assert fset(nx.k_edge_components(G, k=4)) != fset(nx.k_edge_subgraphs(G, k=4)) + + # For k=5 they are not the same + assert fset(nx.k_edge_components(G, k=5)) != fset(nx.k_edge_subgraphs(G, k=5)) + + # For k=6 they are the same + assert fset(nx.k_edge_components(G, k=6)) == fset(nx.k_edge_subgraphs(G, k=6)) + _check_edge_connectivity(G) + + +# ---------------- +# Undirected tests +# ---------------- + + +def test_directed_aux_graph(): + # Graph similar to the one in + # http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0136264 + a, b, c, d, e, f, g, h, i = "abcdefghi" + dipaths = [ + (a, d, b, f, c), + (a, e, b), + (a, e, b, c, g, b, a), + (c, b), + (f, g, f), + (h, i), + ] + G = nx.DiGraph(it.chain(*[pairwise(path) for path in dipaths])) + aux_graph = EdgeComponentAuxGraph.construct(G) + + components_1 = fset(aux_graph.k_edge_subgraphs(k=1)) + target_1 = fset([{a, b, c, d, e, f, g}, {h}, {i}]) + assert target_1 == components_1 + + # Check that the directed case for k=1 agrees with SCCs + alt_1 = fset(nx.strongly_connected_components(G)) + assert alt_1 == components_1 + + components_2 = fset(aux_graph.k_edge_subgraphs(k=2)) + target_2 = fset([{i}, {e}, {d}, {b, c, f, g}, {h}, {a}]) + assert target_2 == components_2 + + components_3 = fset(aux_graph.k_edge_subgraphs(k=3)) + target_3 = fset([{a}, {b}, {c}, {d}, {e}, {f}, {g}, {h}, {i}]) + assert target_3 == components_3 + + +def test_random_gnp_directed(): + # seeds = [3894723670, 500186844, 267231174, 2181982262, 1116750056] + seeds = [21] + for seed in seeds: + G = nx.gnp_random_graph(20, 0.2, directed=True, seed=seed) + _check_edge_connectivity(G) + + +def test_configuration_directed(): + # seeds = [671221681, 2403749451, 124433910, 672335939, 1193127215] + seeds = [67] + for seed in seeds: + deg_seq = nx.random_powerlaw_tree_sequence(20, seed=seed, tries=5000) + G = nx.DiGraph(nx.configuration_model(deg_seq, seed=seed)) + G.remove_edges_from(nx.selfloop_edges(G)) + _check_edge_connectivity(G) + + +def test_shell_directed(): + # seeds = [3134027055, 4079264063, 1350769518, 1405643020, 530038094] + seeds = [31] + for seed in seeds: + constructor = [(12, 70, 0.8), (15, 40, 0.6)] + G = nx.random_shell_graph(constructor, seed=seed).to_directed() + _check_edge_connectivity(G) + + +def test_karate_directed(): + G = nx.karate_club_graph().to_directed() + _check_edge_connectivity(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcomponents.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcomponents.py new file mode 100644 index 0000000000000000000000000000000000000000..8839bbaca2e64057525e0edc027a932d15635965 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcomponents.py @@ -0,0 +1,323 @@ +# Test for Moody and White k-components algorithm +import pytest + +import networkx as nx +from networkx.algorithms import flow +from networkx.algorithms.connectivity.kcomponents import ( + _consolidate, + build_k_number_dict, +) + +FLOW_FUNCS = ( + flow.boykov_kolmogorov, + flow.dinitz, + flow.edmonds_karp, + flow.preflow_push, + flow.shortest_augmenting_path, +) + + +## +# A nice synthetic graph +## +def torrents_and_ferraro_graph(): + # Graph from https://arxiv.org/pdf/1503.04476v1 p.26 + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") + labels = {v: k for k, v in rlabels.items()} + + for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing a node + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + # This edge makes the graph biconnected; it's + # needed because K5s share only one node. + G.add_edge(new_node + 16, new_node + 8) + + for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing two nodes + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + nbrs2 = G[new_node + 9] + G.remove_node(new_node + 9) + for nbr in nbrs2: + G.add_edge(new_node + 18, nbr) + return G + + +def test_directed(): + with pytest.raises(nx.NetworkXNotImplemented): + G = nx.gnp_random_graph(10, 0.2, directed=True, seed=42) + nx.k_components(G) + + +def test_empty_k_components(): + G = nx.empty_graph(5) + assert nx.k_components(G) == {} + + +@pytest.mark.parametrize("flow_func", FLOW_FUNCS) +def test_k_components_alternative_flow_func(flow_func): + G = nx.lollipop_graph(5, 5) + result = nx.k_components(G, flow_func=flow_func) + _check_connectivity(G, result) + + +# Helper function +def _check_connectivity(G, k_components): + for k, components in k_components.items(): + if k < 3: + continue + # check that k-components have node connectivity >= k. + for component in components: + C = G.subgraph(component) + K = nx.node_connectivity(C) + assert K >= k + + +@pytest.mark.slow +def test_torrents_and_ferraro_graph(): + G = torrents_and_ferraro_graph() + result = nx.k_components(G) + _check_connectivity(G, result) + + # In this example graph there are 8 3-components, 4 with 15 nodes + # and 4 with 5 nodes. + assert len(result[3]) == 8 + assert len([c for c in result[3] if len(c) == 15]) == 4 + assert len([c for c in result[3] if len(c) == 5]) == 4 + # There are also 8 4-components all with 5 nodes. + assert len(result[4]) == 8 + assert all(len(c) == 5 for c in result[4]) + + +@pytest.mark.parametrize( + ("n", "p"), [(10, 0.6), pytest.param(50, 0.2, marks=pytest.mark.slow)] +) +def test_random_gnp(n, p): + G = nx.gnp_random_graph(n, p, seed=42) + result = nx.k_components(G) + _check_connectivity(G, result) + + +@pytest.mark.parametrize( + "constructor", + [ + [(5, 8, 0.8), (8, 15, 0.6), (5, 24, 0.2)], + pytest.param([(20, 80, 0.8), (80, 180, 0.6)], marks=pytest.mark.slow), + ], +) +def test_shell(constructor): + G = nx.random_shell_graph(constructor, seed=42) + result = nx.k_components(G) + _check_connectivity(G, result) + + +def test_configuration(): + deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72) + G = nx.Graph(nx.configuration_model(deg_seq)) + G.remove_edges_from(nx.selfloop_edges(G)) + result = nx.k_components(G) + _check_connectivity(G, result) + + +def test_karate(): + G = nx.karate_club_graph() + result = nx.k_components(G) + _check_connectivity(G, result) + + +def test_karate_component_number(): + karate_k_num = { + 0: 4, + 1: 4, + 2: 4, + 3: 4, + 4: 3, + 5: 3, + 6: 3, + 7: 4, + 8: 4, + 9: 2, + 10: 3, + 11: 1, + 12: 2, + 13: 4, + 14: 2, + 15: 2, + 16: 2, + 17: 2, + 18: 2, + 19: 3, + 20: 2, + 21: 2, + 22: 2, + 23: 3, + 24: 3, + 25: 3, + 26: 2, + 27: 3, + 28: 3, + 29: 3, + 30: 4, + 31: 3, + 32: 4, + 33: 4, + } + G = nx.karate_club_graph() + k_components = nx.k_components(G) + k_num = build_k_number_dict(k_components) + assert karate_k_num == k_num + + +def test_davis_southern_women(): + G = nx.davis_southern_women_graph() + result = nx.k_components(G) + _check_connectivity(G, result) + + +def test_davis_southern_women_detail_3_and_4(): + solution = { + 3: [ + { + "Nora Fayette", + "E10", + "Myra Liddel", + "E12", + "E14", + "Frances Anderson", + "Evelyn Jefferson", + "Ruth DeSand", + "Helen Lloyd", + "Eleanor Nye", + "E9", + "E8", + "E5", + "E4", + "E7", + "E6", + "E1", + "Verne Sanderson", + "E3", + "E2", + "Theresa Anderson", + "Pearl Oglethorpe", + "Katherina Rogers", + "Brenda Rogers", + "E13", + "Charlotte McDowd", + "Sylvia Avondale", + "Laura Mandeville", + } + ], + 4: [ + { + "Nora Fayette", + "E10", + "Verne Sanderson", + "E12", + "Frances Anderson", + "Evelyn Jefferson", + "Ruth DeSand", + "Helen Lloyd", + "Eleanor Nye", + "E9", + "E8", + "E5", + "E4", + "E7", + "E6", + "Myra Liddel", + "E3", + "Theresa Anderson", + "Katherina Rogers", + "Brenda Rogers", + "Charlotte McDowd", + "Sylvia Avondale", + "Laura Mandeville", + } + ], + } + G = nx.davis_southern_women_graph() + result = nx.k_components(G) + for k, components in result.items(): + if k < 3: + continue + assert len(components) == len(solution[k]) + for component in components: + assert component in solution[k] + + +def test_set_consolidation_rosettacode(): + # Tests from http://rosettacode.org/wiki/Set_consolidation + def list_of_sets_equal(result, solution): + assert {frozenset(s) for s in result} == {frozenset(s) for s in solution} + + question = [{"A", "B"}, {"C", "D"}] + solution = [{"A", "B"}, {"C", "D"}] + list_of_sets_equal(_consolidate(question, 1), solution) + question = [{"A", "B"}, {"B", "C"}] + solution = [{"A", "B", "C"}] + list_of_sets_equal(_consolidate(question, 1), solution) + question = [{"A", "B"}, {"C", "D"}, {"D", "B"}] + solution = [{"A", "C", "B", "D"}] + list_of_sets_equal(_consolidate(question, 1), solution) + question = [{"H", "I", "K"}, {"A", "B"}, {"C", "D"}, {"D", "B"}, {"F", "G", "H"}] + solution = [{"A", "C", "B", "D"}, {"G", "F", "I", "H", "K"}] + list_of_sets_equal(_consolidate(question, 1), solution) + question = [ + {"A", "H"}, + {"H", "I", "K"}, + {"A", "B"}, + {"C", "D"}, + {"D", "B"}, + {"F", "G", "H"}, + ] + solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}] + list_of_sets_equal(_consolidate(question, 1), solution) + question = [ + {"H", "I", "K"}, + {"A", "B"}, + {"C", "D"}, + {"D", "B"}, + {"F", "G", "H"}, + {"A", "H"}, + ] + solution = [{"A", "C", "B", "D", "G", "F", "I", "H", "K"}] + list_of_sets_equal(_consolidate(question, 1), solution) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcutsets.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcutsets.py new file mode 100644 index 0000000000000000000000000000000000000000..644b378cd70179703292955b6e7414c695a6939e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_kcutsets.py @@ -0,0 +1,280 @@ +# Jordi Torrents +# Test for k-cutsets +import itertools + +import pytest + +import networkx as nx +from networkx.algorithms import flow +from networkx.algorithms.connectivity.kcutsets import _is_separating_set + +MAX_CUTSETS_TO_TEST = 4 # originally 100. cut to decrease testing time + +flow_funcs = [ + flow.boykov_kolmogorov, + flow.dinitz, + flow.edmonds_karp, + flow.preflow_push, + flow.shortest_augmenting_path, +] + + +## +# Some nice synthetic graphs +## +def graph_example_1(): + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") + labels = {v: k for k, v in rlabels.items()} + + for nodes in [ + (labels[(0, 0)], labels[(1, 0)]), + (labels[(0, 4)], labels[(1, 4)]), + (labels[(3, 0)], labels[(4, 0)]), + (labels[(3, 4)], labels[(4, 4)]), + ]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing a node + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + G.add_edge(new_node + 16, new_node + 5) + return G + + +def torrents_and_ferraro_graph(): + G = nx.convert_node_labels_to_integers( + nx.grid_graph([5, 5]), label_attribute="labels" + ) + rlabels = nx.get_node_attributes(G, "labels") + labels = {v: k for k, v in rlabels.items()} + + for nodes in [(labels[(0, 4)], labels[(1, 4)]), (labels[(3, 4)], labels[(4, 4)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing a node + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + # Commenting this makes the graph not biconnected !! + # This stupid mistake make one reviewer very angry :P + G.add_edge(new_node + 16, new_node + 8) + + for nodes in [(labels[(0, 0)], labels[(1, 0)]), (labels[(3, 0)], labels[(4, 0)])]: + new_node = G.order() + 1 + # Petersen graph is triconnected + P = nx.petersen_graph() + G = nx.disjoint_union(G, P) + # Add two edges between the grid and P + G.add_edge(new_node + 1, nodes[0]) + G.add_edge(new_node, nodes[1]) + # K5 is 4-connected + K = nx.complete_graph(5) + G = nx.disjoint_union(G, K) + # Add three edges between P and K5 + G.add_edge(new_node + 2, new_node + 11) + G.add_edge(new_node + 3, new_node + 12) + G.add_edge(new_node + 4, new_node + 13) + # Add another K5 sharing two nodes + G = nx.disjoint_union(G, K) + nbrs = G[new_node + 10] + G.remove_node(new_node + 10) + for nbr in nbrs: + G.add_edge(new_node + 17, nbr) + nbrs2 = G[new_node + 9] + G.remove_node(new_node + 9) + for nbr in nbrs2: + G.add_edge(new_node + 18, nbr) + return G + + +# Helper function +def _check_separating_sets(G): + for cc in nx.connected_components(G): + if len(cc) < 3: + continue + Gc = G.subgraph(cc) + node_conn = nx.node_connectivity(Gc) + all_cuts = nx.all_node_cuts(Gc) + # Only test a limited number of cut sets to reduce test time. + for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST): + assert node_conn == len(cut) + assert not nx.is_connected(nx.restricted_view(G, cut, [])) + + +@pytest.mark.slow +def test_torrents_and_ferraro_graph(): + G = torrents_and_ferraro_graph() + _check_separating_sets(G) + + +def test_example_1(): + G = graph_example_1() + _check_separating_sets(G) + + +def test_random_gnp(): + G = nx.gnp_random_graph(100, 0.1, seed=42) + _check_separating_sets(G) + + +def test_shell(): + constructor = [(20, 80, 0.8), (80, 180, 0.6)] + G = nx.random_shell_graph(constructor, seed=42) + _check_separating_sets(G) + + +def test_configuration(): + deg_seq = nx.random_powerlaw_tree_sequence(100, tries=5, seed=72) + G = nx.Graph(nx.configuration_model(deg_seq)) + G.remove_edges_from(nx.selfloop_edges(G)) + _check_separating_sets(G) + + +def test_karate(): + G = nx.karate_club_graph() + _check_separating_sets(G) + + +def _generate_no_biconnected(max_attempts=50): + attempts = 0 + while True: + G = nx.fast_gnp_random_graph(100, 0.0575, seed=42) + if nx.is_connected(G) and not nx.is_biconnected(G): + attempts = 0 + yield G + else: + if attempts >= max_attempts: + msg = f"Tried {attempts} times: no suitable Graph." + raise Exception(msg) + else: + attempts += 1 + + +def test_articulation_points(): + Ggen = _generate_no_biconnected() + for i in range(1): # change 1 to 3 or more for more realizations. + G = next(Ggen) + articulation_points = [{a} for a in nx.articulation_points(G)] + for cut in nx.all_node_cuts(G): + assert cut in articulation_points + + +def test_grid_2d_graph(): + # All minimum node cuts of a 2d grid + # are the four pairs of nodes that are + # neighbors of the four corner nodes. + G = nx.grid_2d_graph(5, 5) + solution = [{(0, 1), (1, 0)}, {(3, 0), (4, 1)}, {(3, 4), (4, 3)}, {(0, 3), (1, 4)}] + for cut in nx.all_node_cuts(G): + assert cut in solution + + +def test_disconnected_graph(): + G = nx.fast_gnp_random_graph(100, 0.01, seed=42) + cuts = nx.all_node_cuts(G) + pytest.raises(nx.NetworkXError, next, cuts) + + +@pytest.mark.slow +@pytest.mark.parametrize("G", [nx.grid_2d_graph(4, 4), nx.cycle_graph(5)]) +@pytest.mark.parametrize("flow_func", flow_funcs) +def test_alternative_flow_functions(G, flow_func): + node_conn = nx.node_connectivity(G) + all_cuts = nx.all_node_cuts(G, flow_func=flow_func) + # Only test a limited number of cut sets to reduce test time. + for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST): + assert node_conn == len(cut) + assert not nx.is_connected(nx.restricted_view(G, cut, [])) + + +def test_is_separating_set_complete_graph(): + G = nx.complete_graph(5) + assert _is_separating_set(G, {0, 1, 2, 3}) + + +def test_is_separating_set(): + for i in [5, 10, 15]: + G = nx.star_graph(i) + max_degree_node = max(G, key=G.degree) + assert _is_separating_set(G, {max_degree_node}) + + +def test_non_repeated_cuts(): + # The algorithm was repeating the cut {0, 1} for the giant biconnected + # component of the Karate club graph. + K = nx.karate_club_graph() + bcc = max(list(nx.biconnected_components(K)), key=len) + G = K.subgraph(bcc) + solution = [{32, 33}, {2, 33}, {0, 3}, {0, 1}, {29, 33}] + cuts = list(nx.all_node_cuts(G)) + assert len(solution) == len(cuts) + for cut in cuts: + assert cut in solution + + +def test_cycle_graph(): + G = nx.cycle_graph(5) + solution = [{0, 2}, {0, 3}, {1, 3}, {1, 4}, {2, 4}] + cuts = list(nx.all_node_cuts(G)) + assert len(solution) == len(cuts) + for cut in cuts: + assert cut in solution + + +def test_complete_graph(): + G = nx.complete_graph(5) + assert nx.node_connectivity(G) == 4 + assert list(nx.all_node_cuts(G)) == [] + + +def test_all_node_cuts_simple_case(): + G = nx.complete_graph(5) + G.remove_edges_from([(0, 1), (3, 4)]) + expected = [{0, 1, 2}, {2, 3, 4}] + actual = list(nx.all_node_cuts(G)) + assert len(actual) == len(expected) + for cut in actual: + assert cut in expected + + +def test_all_node_cuts_sap(): + """Non-slow test for `all_node_cuts` using the shortest augmenting path flow.""" + G = nx.cycle_graph(5) + node_conn = nx.node_connectivity(G) + all_cuts = nx.all_node_cuts(G, flow_func=flow.shortest_augmenting_path) + # Only test a limited number of cut sets to reduce test time. + for cut in itertools.islice(all_cuts, MAX_CUTSETS_TO_TEST): + assert node_conn == len(cut) + assert not nx.is_connected(nx.restricted_view(G, cut, [])) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_stoer_wagner.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_stoer_wagner.py new file mode 100644 index 0000000000000000000000000000000000000000..2b9e2bab41eb29067166b6faa331e022d4074ce3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/connectivity/tests/test_stoer_wagner.py @@ -0,0 +1,102 @@ +from itertools import chain + +import pytest + +import networkx as nx + + +def _check_partition(G, cut_value, partition, weight): + assert isinstance(partition, tuple) + assert len(partition) == 2 + assert isinstance(partition[0], list) + assert isinstance(partition[1], list) + assert len(partition[0]) > 0 + assert len(partition[1]) > 0 + assert sum(map(len, partition)) == len(G) + assert set(chain.from_iterable(partition)) == set(G) + partition = tuple(map(set, partition)) + w = 0 + for u, v, e in G.edges(data=True): + if (u in partition[0]) == (v in partition[1]): + w += e.get(weight, 1) + assert w == cut_value + + +def _test_stoer_wagner(G, answer, weight="weight"): + cut_value, partition = nx.stoer_wagner(G, weight, heap=nx.utils.PairingHeap) + assert cut_value == answer + _check_partition(G, cut_value, partition, weight) + cut_value, partition = nx.stoer_wagner(G, weight, heap=nx.utils.BinaryHeap) + assert cut_value == answer + _check_partition(G, cut_value, partition, weight) + + +def test_graph1(): + G = nx.Graph() + G.add_edge("x", "a", weight=3) + G.add_edge("x", "b", weight=1) + G.add_edge("a", "c", weight=3) + G.add_edge("b", "c", weight=5) + G.add_edge("b", "d", weight=4) + G.add_edge("d", "e", weight=2) + G.add_edge("c", "y", weight=2) + G.add_edge("e", "y", weight=3) + _test_stoer_wagner(G, 4) + + +def test_graph2(): + G = nx.Graph() + G.add_edge("x", "a") + G.add_edge("x", "b") + G.add_edge("a", "c") + G.add_edge("b", "c") + G.add_edge("b", "d") + G.add_edge("d", "e") + G.add_edge("c", "y") + G.add_edge("e", "y") + _test_stoer_wagner(G, 2) + + +def test_graph3(): + # Source: + # Stoer, M. and Wagner, F. (1997). "A simple min-cut algorithm". Journal of + # the ACM 44 (4), 585-591. + G = nx.Graph() + G.add_edge(1, 2, weight=2) + G.add_edge(1, 5, weight=3) + G.add_edge(2, 3, weight=3) + G.add_edge(2, 5, weight=2) + G.add_edge(2, 6, weight=2) + G.add_edge(3, 4, weight=4) + G.add_edge(3, 7, weight=2) + G.add_edge(4, 7, weight=2) + G.add_edge(4, 8, weight=2) + G.add_edge(5, 6, weight=3) + G.add_edge(6, 7, weight=1) + G.add_edge(7, 8, weight=3) + _test_stoer_wagner(G, 4) + + +def test_weight_name(): + G = nx.Graph() + G.add_edge(1, 2, weight=1, cost=8) + G.add_edge(1, 3, cost=2) + G.add_edge(2, 3, cost=4) + _test_stoer_wagner(G, 6, weight="cost") + + +def test_exceptions(): + G = nx.Graph() + pytest.raises(nx.NetworkXError, nx.stoer_wagner, G) + G.add_node(1) + pytest.raises(nx.NetworkXError, nx.stoer_wagner, G) + G.add_node(2) + pytest.raises(nx.NetworkXError, nx.stoer_wagner, G) + G.add_edge(1, 2, weight=-2) + pytest.raises(nx.NetworkXError, nx.stoer_wagner, G) + G = nx.DiGraph() + pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G) + G = nx.MultiGraph() + pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G) + G = nx.MultiDiGraph() + pytest.raises(nx.NetworkXNotImplemented, nx.stoer_wagner, G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed1f405824c272e9add6072c260c6a077eb2bd97 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/boykovkolmogorov.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/boykovkolmogorov.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d89f264a35e10d1a3892aefad699382d5073bc5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/boykovkolmogorov.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/capacityscaling.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/capacityscaling.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc2e105817bf9a9c549f841d0683fc236706b439 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/capacityscaling.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/dinitz_alg.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/dinitz_alg.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f93ca9d3725ac8fadd3bf6cb0b669deccd716086 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/dinitz_alg.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9ee28de51bd15f81ca3db1ed5a3bc0e584b9f8c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/edmondskarp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/gomory_hu.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/gomory_hu.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc1a2705a3618f9a22305c67e1d08d3019228e43 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/gomory_hu.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c9bca06602a6596cf3d2c955cc5224baa3ddcbc7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/maxflow.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/mincost.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/mincost.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63251e1a0330c0fecee227be66108eac4eca68de Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/mincost.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb3396597d05cba8b514eba46691719f9a783dce Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/networksimplex.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/preflowpush.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/preflowpush.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75a12cb7768195f500620ff595690c3a54bac062 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/preflowpush.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/shortestaugmentingpath.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/shortestaugmentingpath.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b62af31128d0d5b08654ea678af4ad53ab0ed271 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/shortestaugmentingpath.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a9af5de6832254abfdd5be9a123bebf38bf6030 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/__pycache__/utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3302aad7805bb7401da88d0ed1ef83ee5f9cbcf7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d76c154e0d7bc256d172c95e8df5c2e2df3509ca Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_gomory_hu.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f12a5f2e8b1562bbe42ee7ac21fe56a271ab12cc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow_large_graph.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow_large_graph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6453f91205a5fad16bab5c20b56e1000fbe76ff Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_maxflow_large_graph.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_mincost.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_mincost.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d4a4ec91337e8897d12fe51d635ab3c382d8e41 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_mincost.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_networksimplex.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_networksimplex.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbc815746f44cf79bfcc9686661cc8717e7e3465 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/__pycache__/test_networksimplex.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_gomory_hu.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_gomory_hu.py new file mode 100644 index 0000000000000000000000000000000000000000..1649ec82c719226e9caa68268d8953f7cae6ef74 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_gomory_hu.py @@ -0,0 +1,128 @@ +from itertools import combinations + +import pytest + +import networkx as nx +from networkx.algorithms.flow import ( + boykov_kolmogorov, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +) + +flow_funcs = [ + boykov_kolmogorov, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +] + + +class TestGomoryHuTree: + def minimum_edge_weight(self, T, u, v): + path = nx.shortest_path(T, u, v, weight="weight") + return min((T[u][v]["weight"], (u, v)) for (u, v) in zip(path, path[1:])) + + def compute_cutset(self, G, T_orig, edge): + T = T_orig.copy() + T.remove_edge(*edge) + U, V = list(nx.connected_components(T)) + cutset = set() + for x, nbrs in ((n, G[n]) for n in U): + cutset.update((x, y) for y in nbrs if y in V) + return cutset + + def test_default_flow_function_karate_club_graph(self): + G = nx.karate_club_graph() + nx.set_edge_attributes(G, 1, "capacity") + T = nx.gomory_hu_tree(G) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v) == cut_value + + def test_karate_club_graph(self): + G = nx.karate_club_graph() + nx.set_edge_attributes(G, 1, "capacity") + for flow_func in flow_funcs: + T = nx.gomory_hu_tree(G, flow_func=flow_func) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v) == cut_value + + def test_davis_southern_women_graph(self): + G = nx.davis_southern_women_graph() + nx.set_edge_attributes(G, 1, "capacity") + for flow_func in flow_funcs: + T = nx.gomory_hu_tree(G, flow_func=flow_func) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v) == cut_value + + def test_florentine_families_graph(self): + G = nx.florentine_families_graph() + nx.set_edge_attributes(G, 1, "capacity") + for flow_func in flow_funcs: + T = nx.gomory_hu_tree(G, flow_func=flow_func) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v) == cut_value + + @pytest.mark.slow + def test_les_miserables_graph_cutset(self): + G = nx.les_miserables_graph() + nx.set_edge_attributes(G, 1, "capacity") + for flow_func in flow_funcs: + T = nx.gomory_hu_tree(G, flow_func=flow_func) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v) == cut_value + + def test_karate_club_graph_cutset(self): + G = nx.karate_club_graph() + nx.set_edge_attributes(G, 1, "capacity") + T = nx.gomory_hu_tree(G) + assert nx.is_tree(T) + u, v = 0, 33 + cut_value, edge = self.minimum_edge_weight(T, u, v) + cutset = self.compute_cutset(G, T, edge) + assert cut_value == len(cutset) + + def test_wikipedia_example(self): + # Example from https://en.wikipedia.org/wiki/Gomory%E2%80%93Hu_tree + G = nx.Graph() + G.add_weighted_edges_from( + ( + (0, 1, 1), + (0, 2, 7), + (1, 2, 1), + (1, 3, 3), + (1, 4, 2), + (2, 4, 4), + (3, 4, 1), + (3, 5, 6), + (4, 5, 2), + ) + ) + for flow_func in flow_funcs: + T = nx.gomory_hu_tree(G, capacity="weight", flow_func=flow_func) + assert nx.is_tree(T) + for u, v in combinations(G, 2): + cut_value, edge = self.minimum_edge_weight(T, u, v) + assert nx.minimum_cut_value(G, u, v, capacity="weight") == cut_value + + def test_directed_raises(self): + with pytest.raises(nx.NetworkXNotImplemented): + G = nx.DiGraph() + T = nx.gomory_hu_tree(G) + + def test_empty_raises(self): + with pytest.raises(nx.NetworkXError): + G = nx.empty_graph() + T = nx.gomory_hu_tree(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow.py new file mode 100644 index 0000000000000000000000000000000000000000..d7305a7b6320ef1b55c682386cd7320f33a78994 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow.py @@ -0,0 +1,573 @@ +"""Maximum flow algorithms test suite.""" + +import pytest + +import networkx as nx +from networkx.algorithms.flow import ( + boykov_kolmogorov, + build_flow_dict, + build_residual_network, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +) + +flow_funcs = { + boykov_kolmogorov, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +} + +max_min_funcs = {nx.maximum_flow, nx.minimum_cut} +flow_value_funcs = {nx.maximum_flow_value, nx.minimum_cut_value} +interface_funcs = max_min_funcs | flow_value_funcs +all_funcs = flow_funcs | interface_funcs + + +def compute_cutset(G, partition): + reachable, non_reachable = partition + cutset = set() + for u, nbrs in ((n, G[n]) for n in reachable): + cutset.update((u, v) for v in nbrs if v in non_reachable) + return cutset + + +def validate_flows(G, s, t, flowDict, solnValue, capacity, flow_func): + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert set(G) == set(flowDict), errmsg + for u in G: + assert set(G[u]) == set(flowDict[u]), errmsg + excess = {u: 0 for u in flowDict} + for u in flowDict: + for v, flow in flowDict[u].items(): + if capacity in G[u][v]: + assert flow <= G[u][v][capacity] + assert flow >= 0, errmsg + excess[u] -= flow + excess[v] += flow + for u, exc in excess.items(): + if u == s: + assert exc == -solnValue, errmsg + elif u == t: + assert exc == solnValue, errmsg + else: + assert exc == 0, errmsg + + +def validate_cuts(G, s, t, solnValue, partition, capacity, flow_func): + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert all(n in G for n in partition[0]), errmsg + assert all(n in G for n in partition[1]), errmsg + cutset = compute_cutset(G, partition) + assert all(G.has_edge(u, v) for (u, v) in cutset), errmsg + assert solnValue == sum(G[u][v][capacity] for (u, v) in cutset), errmsg + H = G.copy() + H.remove_edges_from(cutset) + if not G.is_directed(): + assert not nx.is_connected(H), errmsg + else: + assert not nx.is_strongly_connected(H), errmsg + + +def compare_flows_and_cuts(G, s, t, solnValue, capacity="capacity"): + for flow_func in flow_funcs: + errmsg = f"Assertion failed in function: {flow_func.__name__}" + R = flow_func(G, s, t, capacity) + # Test both legacy and new implementations. + flow_value = R.graph["flow_value"] + flow_dict = build_flow_dict(G, R) + assert flow_value == solnValue, errmsg + validate_flows(G, s, t, flow_dict, solnValue, capacity, flow_func) + # Minimum cut + cut_value, partition = nx.minimum_cut( + G, s, t, capacity=capacity, flow_func=flow_func + ) + validate_cuts(G, s, t, solnValue, partition, capacity, flow_func) + + +class TestMaxflowMinCutCommon: + def test_graph1(self): + # Trivial undirected graph + G = nx.Graph() + G.add_edge(1, 2, capacity=1.0) + + # solution flows + # {1: {2: 1.0}, 2: {1: 1.0}} + + compare_flows_and_cuts(G, 1, 2, 1.0) + + def test_graph2(self): + # A more complex undirected graph + # adapted from https://web.archive.org/web/20220815055650/https://www.topcoder.com/thrive/articles/Maximum%20Flow:%20Part%20One + G = nx.Graph() + G.add_edge("x", "a", capacity=3.0) + G.add_edge("x", "b", capacity=1.0) + G.add_edge("a", "c", capacity=3.0) + G.add_edge("b", "c", capacity=5.0) + G.add_edge("b", "d", capacity=4.0) + G.add_edge("d", "e", capacity=2.0) + G.add_edge("c", "y", capacity=2.0) + G.add_edge("e", "y", capacity=3.0) + + # H + # { + # "x": {"a": 3, "b": 1}, + # "a": {"c": 3, "x": 3}, + # "b": {"c": 1, "d": 2, "x": 1}, + # "c": {"a": 3, "b": 1, "y": 2}, + # "d": {"b": 2, "e": 2}, + # "e": {"d": 2, "y": 2}, + # "y": {"c": 2, "e": 2}, + # } + + compare_flows_and_cuts(G, "x", "y", 4.0) + + def test_digraph1(self): + # The classic directed graph example + G = nx.DiGraph() + G.add_edge("a", "b", capacity=1000.0) + G.add_edge("a", "c", capacity=1000.0) + G.add_edge("b", "c", capacity=1.0) + G.add_edge("b", "d", capacity=1000.0) + G.add_edge("c", "d", capacity=1000.0) + + # H + # { + # "a": {"b": 1000.0, "c": 1000.0}, + # "b": {"c": 0, "d": 1000.0}, + # "c": {"d": 1000.0}, + # "d": {}, + # } + + compare_flows_and_cuts(G, "a", "d", 2000.0) + + def test_digraph2(self): + # An example in which some edges end up with zero flow. + G = nx.DiGraph() + G.add_edge("s", "b", capacity=2) + G.add_edge("s", "c", capacity=1) + G.add_edge("c", "d", capacity=1) + G.add_edge("d", "a", capacity=1) + G.add_edge("b", "a", capacity=2) + G.add_edge("a", "t", capacity=2) + + # H + # { + # "s": {"b": 2, "c": 0}, + # "c": {"d": 0}, + # "d": {"a": 0}, + # "b": {"a": 2}, + # "a": {"t": 2}, + # "t": {}, + # } + + compare_flows_and_cuts(G, "s", "t", 2) + + def test_digraph3(self): + # A directed graph example from Cormen et al. + G = nx.DiGraph() + G.add_edge("s", "v1", capacity=16.0) + G.add_edge("s", "v2", capacity=13.0) + G.add_edge("v1", "v2", capacity=10.0) + G.add_edge("v2", "v1", capacity=4.0) + G.add_edge("v1", "v3", capacity=12.0) + G.add_edge("v3", "v2", capacity=9.0) + G.add_edge("v2", "v4", capacity=14.0) + G.add_edge("v4", "v3", capacity=7.0) + G.add_edge("v3", "t", capacity=20.0) + G.add_edge("v4", "t", capacity=4.0) + + # H + # { + # "s": {"v1": 12.0, "v2": 11.0}, + # "v2": {"v1": 0, "v4": 11.0}, + # "v1": {"v2": 0, "v3": 12.0}, + # "v3": {"v2": 0, "t": 19.0}, + # "v4": {"v3": 7.0, "t": 4.0}, + # "t": {}, + # } + + compare_flows_and_cuts(G, "s", "t", 23.0) + + def test_digraph4(self): + # A more complex directed graph + # from https://web.archive.org/web/20220815055650/https://www.topcoder.com/thrive/articles/Maximum%20Flow:%20Part%20One + G = nx.DiGraph() + G.add_edge("x", "a", capacity=3.0) + G.add_edge("x", "b", capacity=1.0) + G.add_edge("a", "c", capacity=3.0) + G.add_edge("b", "c", capacity=5.0) + G.add_edge("b", "d", capacity=4.0) + G.add_edge("d", "e", capacity=2.0) + G.add_edge("c", "y", capacity=2.0) + G.add_edge("e", "y", capacity=3.0) + + # H + # { + # "x": {"a": 2.0, "b": 1.0}, + # "a": {"c": 2.0}, + # "b": {"c": 0, "d": 1.0}, + # "c": {"y": 2.0}, + # "d": {"e": 1.0}, + # "e": {"y": 1.0}, + # "y": {}, + # } + + compare_flows_and_cuts(G, "x", "y", 3.0) + + def test_wikipedia_dinitz_example(self): + # Nice example from https://en.wikipedia.org/wiki/Dinic's_algorithm + G = nx.DiGraph() + G.add_edge("s", 1, capacity=10) + G.add_edge("s", 2, capacity=10) + G.add_edge(1, 3, capacity=4) + G.add_edge(1, 4, capacity=8) + G.add_edge(1, 2, capacity=2) + G.add_edge(2, 4, capacity=9) + G.add_edge(3, "t", capacity=10) + G.add_edge(4, 3, capacity=6) + G.add_edge(4, "t", capacity=10) + + # solution flows + # { + # 1: {2: 0, 3: 4, 4: 6}, + # 2: {4: 9}, + # 3: {"t": 9}, + # 4: {3: 5, "t": 10}, + # "s": {1: 10, 2: 9}, + # "t": {}, + # } + + compare_flows_and_cuts(G, "s", "t", 19) + + def test_optional_capacity(self): + # Test optional capacity parameter. + G = nx.DiGraph() + G.add_edge("x", "a", spam=3.0) + G.add_edge("x", "b", spam=1.0) + G.add_edge("a", "c", spam=3.0) + G.add_edge("b", "c", spam=5.0) + G.add_edge("b", "d", spam=4.0) + G.add_edge("d", "e", spam=2.0) + G.add_edge("c", "y", spam=2.0) + G.add_edge("e", "y", spam=3.0) + + # solution flows + # { + # "x": {"a": 2.0, "b": 1.0}, + # "a": {"c": 2.0}, + # "b": {"c": 0, "d": 1.0}, + # "c": {"y": 2.0}, + # "d": {"e": 1.0}, + # "e": {"y": 1.0}, + # "y": {}, + # } + solnValue = 3.0 + s = "x" + t = "y" + + compare_flows_and_cuts(G, s, t, solnValue, capacity="spam") + + def test_digraph_infcap_edges(self): + # DiGraph with infinite capacity edges + G = nx.DiGraph() + G.add_edge("s", "a") + G.add_edge("s", "b", capacity=30) + G.add_edge("a", "c", capacity=25) + G.add_edge("b", "c", capacity=12) + G.add_edge("a", "t", capacity=60) + G.add_edge("c", "t") + + # H + # { + # "s": {"a": 85, "b": 12}, + # "a": {"c": 25, "t": 60}, + # "b": {"c": 12}, + # "c": {"t": 37}, + # "t": {}, + # } + + compare_flows_and_cuts(G, "s", "t", 97) + + # DiGraph with infinite capacity digon + G = nx.DiGraph() + G.add_edge("s", "a", capacity=85) + G.add_edge("s", "b", capacity=30) + G.add_edge("a", "c") + G.add_edge("c", "a") + G.add_edge("b", "c", capacity=12) + G.add_edge("a", "t", capacity=60) + G.add_edge("c", "t", capacity=37) + + # H + # { + # "s": {"a": 85, "b": 12}, + # "a": {"c": 25, "t": 60}, + # "c": {"a": 0, "t": 37}, + # "b": {"c": 12}, + # "t": {}, + # } + + compare_flows_and_cuts(G, "s", "t", 97) + + def test_digraph_infcap_path(self): + # Graph with infinite capacity (s, t)-path + G = nx.DiGraph() + G.add_edge("s", "a") + G.add_edge("s", "b", capacity=30) + G.add_edge("a", "c") + G.add_edge("b", "c", capacity=12) + G.add_edge("a", "t", capacity=60) + G.add_edge("c", "t") + + for flow_func in all_funcs: + pytest.raises(nx.NetworkXUnbounded, flow_func, G, "s", "t") + + def test_graph_infcap_edges(self): + # Undirected graph with infinite capacity edges + G = nx.Graph() + G.add_edge("s", "a") + G.add_edge("s", "b", capacity=30) + G.add_edge("a", "c", capacity=25) + G.add_edge("b", "c", capacity=12) + G.add_edge("a", "t", capacity=60) + G.add_edge("c", "t") + + # H + # { + # "s": {"a": 85, "b": 12}, + # "a": {"c": 25, "s": 85, "t": 60}, + # "b": {"c": 12, "s": 12}, + # "c": {"a": 25, "b": 12, "t": 37}, + # "t": {"a": 60, "c": 37}, + # } + + compare_flows_and_cuts(G, "s", "t", 97) + + def test_digraph5(self): + # From ticket #429 by mfrasca. + G = nx.DiGraph() + G.add_edge("s", "a", capacity=2) + G.add_edge("s", "b", capacity=2) + G.add_edge("a", "b", capacity=5) + G.add_edge("a", "t", capacity=1) + G.add_edge("b", "a", capacity=1) + G.add_edge("b", "t", capacity=3) + # flow solution + # { + # "a": {"b": 1, "t": 1}, + # "b": {"a": 0, "t": 3}, + # "s": {"a": 2, "b": 2}, + # "t": {}, + # } + compare_flows_and_cuts(G, "s", "t", 4) + + def test_disconnected(self): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity") + G.remove_node(1) + assert nx.maximum_flow_value(G, 0, 3) == 0 + # flow solution + # {0: {}, 2: {3: 0}, 3: {2: 0}} + compare_flows_and_cuts(G, 0, 3, 0) + + def test_source_target_not_in_graph(self): + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity") + G.remove_node(0) + for flow_func in all_funcs: + pytest.raises(nx.NetworkXError, flow_func, G, 0, 3) + G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity") + G.remove_node(3) + for flow_func in all_funcs: + pytest.raises(nx.NetworkXError, flow_func, G, 0, 3) + + def test_source_target_coincide(self): + G = nx.Graph() + G.add_node(0) + for flow_func in all_funcs: + pytest.raises(nx.NetworkXError, flow_func, G, 0, 0) + + def test_multigraphs_raise(self): + G = nx.MultiGraph() + M = nx.MultiDiGraph() + G.add_edges_from([(0, 1), (1, 0)], capacity=True) + for flow_func in all_funcs: + pytest.raises(nx.NetworkXError, flow_func, G, 0, 0) + + +class TestMaxFlowMinCutInterface: + def setup_method(self): + G = nx.DiGraph() + G.add_edge("x", "a", capacity=3.0) + G.add_edge("x", "b", capacity=1.0) + G.add_edge("a", "c", capacity=3.0) + G.add_edge("b", "c", capacity=5.0) + G.add_edge("b", "d", capacity=4.0) + G.add_edge("d", "e", capacity=2.0) + G.add_edge("c", "y", capacity=2.0) + G.add_edge("e", "y", capacity=3.0) + self.G = G + H = nx.DiGraph() + H.add_edge(0, 1, capacity=1.0) + H.add_edge(1, 2, capacity=1.0) + self.H = H + + def test_flow_func_not_callable(self): + elements = ["this_should_be_callable", 10, {1, 2, 3}] + G = nx.Graph() + G.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1)], weight="capacity") + for flow_func in interface_funcs: + for element in elements: + pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element) + pytest.raises(nx.NetworkXError, flow_func, G, 0, 1, flow_func=element) + + def test_flow_func_parameters(self): + G = self.G + fv = 3.0 + for interface_func in interface_funcs: + for flow_func in flow_funcs: + errmsg = ( + f"Assertion failed in function: {flow_func.__name__} " + f"in interface {interface_func.__name__}" + ) + result = interface_func(G, "x", "y", flow_func=flow_func) + if interface_func in max_min_funcs: + result = result[0] + assert fv == result, errmsg + + def test_minimum_cut_no_cutoff(self): + G = self.G + pytest.raises( + nx.NetworkXError, + nx.minimum_cut, + G, + "x", + "y", + flow_func=preflow_push, + cutoff=1.0, + ) + pytest.raises( + nx.NetworkXError, + nx.minimum_cut_value, + G, + "x", + "y", + flow_func=preflow_push, + cutoff=1.0, + ) + + def test_kwargs(self): + G = self.H + fv = 1.0 + to_test = ( + (shortest_augmenting_path, {"two_phase": True}), + (preflow_push, {"global_relabel_freq": 5}), + ) + for interface_func in interface_funcs: + for flow_func, kwargs in to_test: + errmsg = ( + f"Assertion failed in function: {flow_func.__name__} " + f"in interface {interface_func.__name__}" + ) + result = interface_func(G, 0, 2, flow_func=flow_func, **kwargs) + if interface_func in max_min_funcs: + result = result[0] + assert fv == result, errmsg + + def test_kwargs_default_flow_func(self): + G = self.H + for interface_func in interface_funcs: + pytest.raises( + nx.NetworkXError, interface_func, G, 0, 1, global_relabel_freq=2 + ) + + def test_reusing_residual(self): + G = self.G + fv = 3.0 + s, t = "x", "y" + R = build_residual_network(G, "capacity") + for interface_func in interface_funcs: + for flow_func in flow_funcs: + errmsg = ( + f"Assertion failed in function: {flow_func.__name__} " + f"in interface {interface_func.__name__}" + ) + for i in range(3): + result = interface_func( + G, "x", "y", flow_func=flow_func, residual=R + ) + if interface_func in max_min_funcs: + result = result[0] + assert fv == result, errmsg + + +# Tests specific to one algorithm +def test_preflow_push_global_relabel_freq(): + G = nx.DiGraph() + G.add_edge(1, 2, capacity=1) + R = preflow_push(G, 1, 2, global_relabel_freq=None) + assert R.graph["flow_value"] == 1 + pytest.raises(nx.NetworkXError, preflow_push, G, 1, 2, global_relabel_freq=-1) + + +def test_preflow_push_makes_enough_space(): + # From ticket #1542 + G = nx.DiGraph() + nx.add_path(G, [0, 1, 3], capacity=1) + nx.add_path(G, [1, 2, 3], capacity=1) + R = preflow_push(G, 0, 3, value_only=False) + assert R.graph["flow_value"] == 1 + + +def test_shortest_augmenting_path_two_phase(): + k = 5 + p = 1000 + G = nx.DiGraph() + for i in range(k): + G.add_edge("s", (i, 0), capacity=1) + nx.add_path(G, ((i, j) for j in range(p)), capacity=1) + G.add_edge((i, p - 1), "t", capacity=1) + R = shortest_augmenting_path(G, "s", "t", two_phase=True) + assert R.graph["flow_value"] == k + R = shortest_augmenting_path(G, "s", "t", two_phase=False) + assert R.graph["flow_value"] == k + + +class TestCutoff: + def test_cutoff(self): + k = 5 + p = 1000 + G = nx.DiGraph() + for i in range(k): + G.add_edge("s", (i, 0), capacity=2) + nx.add_path(G, ((i, j) for j in range(p)), capacity=2) + G.add_edge((i, p - 1), "t", capacity=2) + R = shortest_augmenting_path(G, "s", "t", two_phase=True, cutoff=k) + assert k <= R.graph["flow_value"] <= (2 * k) + R = shortest_augmenting_path(G, "s", "t", two_phase=False, cutoff=k) + assert k <= R.graph["flow_value"] <= (2 * k) + R = edmonds_karp(G, "s", "t", cutoff=k) + assert k <= R.graph["flow_value"] <= (2 * k) + R = dinitz(G, "s", "t", cutoff=k) + assert k <= R.graph["flow_value"] <= (2 * k) + R = boykov_kolmogorov(G, "s", "t", cutoff=k) + assert k <= R.graph["flow_value"] <= (2 * k) + + def test_complete_graph_cutoff(self): + G = nx.complete_graph(5) + nx.set_edge_attributes(G, {(u, v): 1 for u, v in G.edges()}, "capacity") + for flow_func in [ + shortest_augmenting_path, + edmonds_karp, + dinitz, + boykov_kolmogorov, + ]: + for cutoff in [3, 2, 1]: + result = nx.maximum_flow_value( + G, 0, 4, flow_func=flow_func, cutoff=cutoff + ) + assert cutoff == result, f"cutoff error in {flow_func.__name__}" diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..6165da891b4b30704cc1c2697cf719ef080163ee --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_maxflow_large_graph.py @@ -0,0 +1,155 @@ +"""Maximum flow algorithms test suite on large graphs.""" + +import bz2 +import importlib.resources +import pickle + +import pytest + +import networkx as nx +from networkx.algorithms.flow import ( + boykov_kolmogorov, + build_flow_dict, + build_residual_network, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +) + +flow_funcs = [ + boykov_kolmogorov, + dinitz, + edmonds_karp, + preflow_push, + shortest_augmenting_path, +] + + +def gen_pyramid(N): + # This graph admits a flow of value 1 for which every arc is at + # capacity (except the arcs incident to the sink which have + # infinite capacity). + G = nx.DiGraph() + + for i in range(N - 1): + cap = 1.0 / (i + 2) + for j in range(i + 1): + G.add_edge((i, j), (i + 1, j), capacity=cap) + cap = 1.0 / (i + 1) - cap + G.add_edge((i, j), (i + 1, j + 1), capacity=cap) + cap = 1.0 / (i + 2) - cap + + for j in range(N): + G.add_edge((N - 1, j), "t") + + return G + + +def read_graph(name): + fname = ( + importlib.resources.files("networkx.algorithms.flow.tests") + / f"{name}.gpickle.bz2" + ) + + with bz2.BZ2File(fname, "rb") as f: + G = pickle.load(f) + return G + + +def validate_flows(G, s, t, soln_value, R, flow_func): + flow_value = R.graph["flow_value"] + flow_dict = build_flow_dict(G, R) + errmsg = f"Assertion failed in function: {flow_func.__name__}" + assert soln_value == flow_value, errmsg + assert set(G) == set(flow_dict), errmsg + for u in G: + assert set(G[u]) == set(flow_dict[u]), errmsg + excess = {u: 0 for u in flow_dict} + for u in flow_dict: + for v, flow in flow_dict[u].items(): + assert flow <= G[u][v].get("capacity", float("inf")), errmsg + assert flow >= 0, errmsg + excess[u] -= flow + excess[v] += flow + for u, exc in excess.items(): + if u == s: + assert exc == -soln_value, errmsg + elif u == t: + assert exc == soln_value, errmsg + else: + assert exc == 0, errmsg + + +class TestMaxflowLargeGraph: + def test_complete_graph(self): + N = 50 + G = nx.complete_graph(N) + nx.set_edge_attributes(G, 5, "capacity") + R = build_residual_network(G, "capacity") + kwargs = {"residual": R} + + for flow_func in flow_funcs: + kwargs["flow_func"] = flow_func + errmsg = f"Assertion failed in function: {flow_func.__name__}" + flow_value = nx.maximum_flow_value(G, 1, 2, **kwargs) + assert flow_value == 5 * (N - 1), errmsg + + def test_pyramid(self): + N = 10 + # N = 100 # this gives a graph with 5051 nodes + G = gen_pyramid(N) + R = build_residual_network(G, "capacity") + kwargs = {"residual": R} + + for flow_func in flow_funcs: + kwargs["flow_func"] = flow_func + errmsg = f"Assertion failed in function: {flow_func.__name__}" + flow_value = nx.maximum_flow_value(G, (0, 0), "t", **kwargs) + assert flow_value == pytest.approx(1.0, abs=1e-7) + + def test_gl1(self): + G = read_graph("gl1") + s = 1 + t = len(G) + R = build_residual_network(G, "capacity") + kwargs = {"residual": R} + + # do one flow_func to save time + flow_func = flow_funcs[0] + validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), flow_func) + + # for flow_func in flow_funcs: + # validate_flows(G, s, t, 156545, flow_func(G, s, t, **kwargs), + # flow_func) + + @pytest.mark.slow + def test_gw1(self): + G = read_graph("gw1") + s = 1 + t = len(G) + R = build_residual_network(G, "capacity") + kwargs = {"residual": R} + + for flow_func in flow_funcs: + validate_flows(G, s, t, 1202018, flow_func(G, s, t, **kwargs), flow_func) + + def test_wlm3(self): + G = read_graph("wlm3") + s = 1 + t = len(G) + R = build_residual_network(G, "capacity") + kwargs = {"residual": R} + + # do one flow_func to save time + flow_func = flow_funcs[0] + validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), flow_func) + + # for flow_func in flow_funcs: + # validate_flows(G, s, t, 11875108, flow_func(G, s, t, **kwargs), + # flow_func) + + def test_preflow_push_global_relabel(self): + G = read_graph("gw1") + R = preflow_push(G, 1, len(G), global_relabel_freq=50) + assert R.graph["flow_value"] == 1202018 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_mincost.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_mincost.py new file mode 100644 index 0000000000000000000000000000000000000000..edc6262213300b3853419b2604e49722c418934a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_mincost.py @@ -0,0 +1,475 @@ +import bz2 +import importlib.resources +import pickle + +import pytest + +import networkx as nx + + +class TestMinCostFlow: + def test_simple_digraph(self): + G = nx.DiGraph() + G.add_node("a", demand=-5) + G.add_node("d", demand=5) + G.add_edge("a", "b", weight=3, capacity=4) + G.add_edge("a", "c", weight=6, capacity=10) + G.add_edge("b", "d", weight=1, capacity=9) + G.add_edge("c", "d", weight=2, capacity=5) + flowCost, H = nx.network_simplex(G) + soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}} + assert flowCost == 24 + assert nx.min_cost_flow_cost(G) == 24 + assert H == soln + assert nx.min_cost_flow(G) == soln + assert nx.cost_of_flow(G, H) == 24 + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 24 + assert nx.cost_of_flow(G, H) == 24 + assert H == soln + + def test_negcycle_infcap(self): + G = nx.DiGraph() + G.add_node("s", demand=-5) + G.add_node("t", demand=5) + G.add_edge("s", "a", weight=1, capacity=3) + G.add_edge("a", "b", weight=3) + G.add_edge("c", "a", weight=-6) + G.add_edge("b", "d", weight=1) + G.add_edge("d", "c", weight=-2) + G.add_edge("d", "t", weight=1, capacity=3) + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G) + + def test_sum_demands_not_zero(self): + G = nx.DiGraph() + G.add_node("s", demand=-5) + G.add_node("t", demand=4) + G.add_edge("s", "a", weight=1, capacity=3) + G.add_edge("a", "b", weight=3) + G.add_edge("a", "c", weight=-6) + G.add_edge("b", "d", weight=1) + G.add_edge("c", "d", weight=-2) + G.add_edge("d", "t", weight=1, capacity=3) + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + + def test_no_flow_satisfying_demands(self): + G = nx.DiGraph() + G.add_node("s", demand=-5) + G.add_node("t", demand=5) + G.add_edge("s", "a", weight=1, capacity=3) + G.add_edge("a", "b", weight=3) + G.add_edge("a", "c", weight=-6) + G.add_edge("b", "d", weight=1) + G.add_edge("c", "d", weight=-2) + G.add_edge("d", "t", weight=1, capacity=3) + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + + def test_transshipment(self): + G = nx.DiGraph() + G.add_node("a", demand=1) + G.add_node("b", demand=-2) + G.add_node("c", demand=-2) + G.add_node("d", demand=3) + G.add_node("e", demand=-4) + G.add_node("f", demand=-4) + G.add_node("g", demand=3) + G.add_node("h", demand=2) + G.add_node("r", demand=3) + G.add_edge("a", "c", weight=3) + G.add_edge("r", "a", weight=2) + G.add_edge("b", "a", weight=9) + G.add_edge("r", "c", weight=0) + G.add_edge("b", "r", weight=-6) + G.add_edge("c", "d", weight=5) + G.add_edge("e", "r", weight=4) + G.add_edge("e", "f", weight=3) + G.add_edge("h", "b", weight=4) + G.add_edge("f", "d", weight=7) + G.add_edge("f", "h", weight=12) + G.add_edge("g", "d", weight=12) + G.add_edge("f", "g", weight=-1) + G.add_edge("h", "g", weight=-10) + flowCost, H = nx.network_simplex(G) + soln = { + "a": {"c": 0}, + "b": {"a": 0, "r": 2}, + "c": {"d": 3}, + "d": {}, + "e": {"r": 3, "f": 1}, + "f": {"d": 0, "g": 3, "h": 2}, + "g": {"d": 0}, + "h": {"b": 0, "g": 0}, + "r": {"a": 1, "c": 1}, + } + assert flowCost == 41 + assert nx.min_cost_flow_cost(G) == 41 + assert H == soln + assert nx.min_cost_flow(G) == soln + assert nx.cost_of_flow(G, H) == 41 + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 41 + assert nx.cost_of_flow(G, H) == 41 + assert H == soln + + def test_max_flow_min_cost(self): + G = nx.DiGraph() + G.add_edge("s", "a", bandwidth=6) + G.add_edge("s", "c", bandwidth=10, cost=10) + G.add_edge("a", "b", cost=6) + G.add_edge("b", "d", bandwidth=8, cost=7) + G.add_edge("c", "d", cost=10) + G.add_edge("d", "t", bandwidth=5, cost=5) + soln = { + "s": {"a": 5, "c": 0}, + "a": {"b": 5}, + "b": {"d": 5}, + "c": {"d": 0}, + "d": {"t": 5}, + "t": {}, + } + flow = nx.max_flow_min_cost(G, "s", "t", capacity="bandwidth", weight="cost") + assert flow == soln + assert nx.cost_of_flow(G, flow, weight="cost") == 90 + + G.add_edge("t", "s", cost=-100) + flowCost, flow = nx.capacity_scaling(G, capacity="bandwidth", weight="cost") + G.remove_edge("t", "s") + assert flowCost == -410 + assert flow["t"]["s"] == 5 + del flow["t"]["s"] + assert flow == soln + assert nx.cost_of_flow(G, flow, weight="cost") == 90 + + def test_digraph1(self): + # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied + # Mathematical Programming. Addison-Wesley, 1977. + G = nx.DiGraph() + G.add_node(1, demand=-20) + G.add_node(4, demand=5) + G.add_node(5, demand=15) + G.add_edges_from( + [ + (1, 2, {"capacity": 15, "weight": 4}), + (1, 3, {"capacity": 8, "weight": 4}), + (2, 3, {"weight": 2}), + (2, 4, {"capacity": 4, "weight": 2}), + (2, 5, {"capacity": 10, "weight": 6}), + (3, 4, {"capacity": 15, "weight": 1}), + (3, 5, {"capacity": 5, "weight": 3}), + (4, 5, {"weight": 2}), + (5, 3, {"capacity": 4, "weight": 1}), + ] + ) + flowCost, H = nx.network_simplex(G) + soln = { + 1: {2: 12, 3: 8}, + 2: {3: 8, 4: 4, 5: 0}, + 3: {4: 11, 5: 5}, + 4: {5: 10}, + 5: {3: 0}, + } + assert flowCost == 150 + assert nx.min_cost_flow_cost(G) == 150 + assert H == soln + assert nx.min_cost_flow(G) == soln + assert nx.cost_of_flow(G, H) == 150 + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 150 + assert H == soln + assert nx.cost_of_flow(G, H) == 150 + + def test_digraph2(self): + # Example from ticket #430 from mfrasca. Original source: + # http://www.cs.princeton.edu/courses/archive/spr03/cs226/lectures/mincost.4up.pdf, slide 11. + G = nx.DiGraph() + G.add_edge("s", 1, capacity=12) + G.add_edge("s", 2, capacity=6) + G.add_edge("s", 3, capacity=14) + G.add_edge(1, 2, capacity=11, weight=4) + G.add_edge(2, 3, capacity=9, weight=6) + G.add_edge(1, 4, capacity=5, weight=5) + G.add_edge(1, 5, capacity=2, weight=12) + G.add_edge(2, 5, capacity=4, weight=4) + G.add_edge(2, 6, capacity=2, weight=6) + G.add_edge(3, 6, capacity=31, weight=3) + G.add_edge(4, 5, capacity=18, weight=4) + G.add_edge(5, 6, capacity=9, weight=5) + G.add_edge(4, "t", capacity=3) + G.add_edge(5, "t", capacity=7) + G.add_edge(6, "t", capacity=22) + flow = nx.max_flow_min_cost(G, "s", "t") + soln = { + 1: {2: 6, 4: 5, 5: 1}, + 2: {3: 6, 5: 4, 6: 2}, + 3: {6: 20}, + 4: {5: 2, "t": 3}, + 5: {6: 0, "t": 7}, + 6: {"t": 22}, + "s": {1: 12, 2: 6, 3: 14}, + "t": {}, + } + assert flow == soln + + G.add_edge("t", "s", weight=-100) + flowCost, flow = nx.capacity_scaling(G) + G.remove_edge("t", "s") + assert flow["t"]["s"] == 32 + assert flowCost == -3007 + del flow["t"]["s"] + assert flow == soln + assert nx.cost_of_flow(G, flow) == 193 + + def test_digraph3(self): + """Combinatorial Optimization: Algorithms and Complexity, + Papadimitriou Steiglitz at page 140 has an example, 7.1, but that + admits multiple solutions, so I alter it a bit. From ticket #430 + by mfrasca.""" + + G = nx.DiGraph() + G.add_edge("s", "a") + G["s"]["a"].update({0: 2, 1: 4}) + G.add_edge("s", "b") + G["s"]["b"].update({0: 2, 1: 1}) + G.add_edge("a", "b") + G["a"]["b"].update({0: 5, 1: 2}) + G.add_edge("a", "t") + G["a"]["t"].update({0: 1, 1: 5}) + G.add_edge("b", "a") + G["b"]["a"].update({0: 1, 1: 3}) + G.add_edge("b", "t") + G["b"]["t"].update({0: 3, 1: 2}) + + "PS.ex.7.1: testing main function" + sol = nx.max_flow_min_cost(G, "s", "t", capacity=0, weight=1) + flow = sum(v for v in sol["s"].values()) + assert 4 == flow + assert 23 == nx.cost_of_flow(G, sol, weight=1) + assert sol["s"] == {"a": 2, "b": 2} + assert sol["a"] == {"b": 1, "t": 1} + assert sol["b"] == {"a": 0, "t": 3} + assert sol["t"] == {} + + G.add_edge("t", "s") + G["t"]["s"].update({1: -100}) + flowCost, sol = nx.capacity_scaling(G, capacity=0, weight=1) + G.remove_edge("t", "s") + flow = sum(v for v in sol["s"].values()) + assert 4 == flow + assert sol["t"]["s"] == 4 + assert flowCost == -377 + del sol["t"]["s"] + assert sol["s"] == {"a": 2, "b": 2} + assert sol["a"] == {"b": 1, "t": 1} + assert sol["b"] == {"a": 0, "t": 3} + assert sol["t"] == {} + assert nx.cost_of_flow(G, sol, weight=1) == 23 + + def test_zero_capacity_edges(self): + """Address issue raised in ticket #617 by arv.""" + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2, {"capacity": 1, "weight": 1}), + (1, 5, {"capacity": 1, "weight": 1}), + (2, 3, {"capacity": 0, "weight": 1}), + (2, 5, {"capacity": 1, "weight": 1}), + (5, 3, {"capacity": 2, "weight": 1}), + (5, 4, {"capacity": 0, "weight": 1}), + (3, 4, {"capacity": 2, "weight": 1}), + ] + ) + G.nodes[1]["demand"] = -1 + G.nodes[2]["demand"] = -1 + G.nodes[4]["demand"] = 2 + + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}} + assert flowCost == 6 + assert nx.min_cost_flow_cost(G) == 6 + assert H == soln + assert nx.min_cost_flow(G) == soln + assert nx.cost_of_flow(G, H) == 6 + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 6 + assert H == soln + assert nx.cost_of_flow(G, H) == 6 + + def test_digon(self): + """Check if digons are handled properly. Taken from ticket + #618 by arv.""" + nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})] + edges = [ + (1, 2, {"capacity": 3, "weight": 600000}), + (2, 1, {"capacity": 2, "weight": 0}), + (2, 3, {"capacity": 5, "weight": 714285}), + (3, 2, {"capacity": 2, "weight": 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}} + assert flowCost == 2857140 + assert nx.min_cost_flow_cost(G) == 2857140 + assert H == soln + assert nx.min_cost_flow(G) == soln + assert nx.cost_of_flow(G, H) == 2857140 + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 2857140 + assert H == soln + assert nx.cost_of_flow(G, H) == 2857140 + + def test_deadend(self): + """Check if one-node cycles are handled properly. Taken from ticket + #2906 from @sshraven.""" + G = nx.DiGraph() + + G.add_nodes_from(range(5), demand=0) + G.nodes[4]["demand"] = -13 + G.nodes[3]["demand"] = 13 + + G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1) + pytest.raises(nx.NetworkXUnfeasible, nx.min_cost_flow, G) + + def test_infinite_capacity_neg_digon(self): + """An infinite capacity negative cost digon results in an unbounded + instance.""" + nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})] + edges = [ + (1, 2, {"weight": -600}), + (2, 1, {"weight": 0}), + (2, 3, {"capacity": 5, "weight": 714285}), + (3, 2, {"capacity": 2, "weight": 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G) + + def test_finite_capacity_neg_digon(self): + """The digon should receive the maximum amount of flow it can handle. + Taken from ticket #749 by @chuongdo.""" + G = nx.DiGraph() + G.add_edge("a", "b", capacity=1, weight=-1) + G.add_edge("b", "a", capacity=1, weight=-1) + min_cost = -2 + assert nx.min_cost_flow_cost(G) == min_cost + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == -2 + assert H == {"a": {"b": 1}, "b": {"a": 1}} + assert nx.cost_of_flow(G, H) == -2 + + def test_multidigraph(self): + """Multidigraphs are acceptable.""" + G = nx.MultiDiGraph() + G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity") + flowCost, H = nx.network_simplex(G) + assert flowCost == 0 + assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}} + + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 0 + assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}} + + def test_negative_selfloops(self): + """Negative selfloops should cause an exception if uncapacitated and + always be saturated otherwise. + """ + G = nx.DiGraph() + G.add_edge(1, 1, weight=-1) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G) + G[1][1]["capacity"] = 2 + flowCost, H = nx.network_simplex(G) + assert flowCost == -2 + assert H == {1: {1: 2}} + flowCost, H = nx.capacity_scaling(G) + assert flowCost == -2 + assert H == {1: {1: 2}} + + G = nx.MultiDiGraph() + G.add_edge(1, 1, "x", weight=-1) + G.add_edge(1, 1, "y", weight=1) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnbounded, nx.capacity_scaling, G) + G[1][1]["x"]["capacity"] = 2 + flowCost, H = nx.network_simplex(G) + assert flowCost == -2 + assert H == {1: {1: {"x": 2, "y": 0}}} + flowCost, H = nx.capacity_scaling(G) + assert flowCost == -2 + assert H == {1: {1: {"x": 2, "y": 0}}} + + def test_bone_shaped(self): + # From #1283 + G = nx.DiGraph() + G.add_node(0, demand=-4) + G.add_node(1, demand=2) + G.add_node(2, demand=2) + G.add_node(3, demand=4) + G.add_node(4, demand=-2) + G.add_node(5, demand=-2) + G.add_edge(0, 1, capacity=4) + G.add_edge(0, 2, capacity=4) + G.add_edge(4, 3, capacity=4) + G.add_edge(5, 3, capacity=4) + G.add_edge(0, 3, capacity=0) + flowCost, H = nx.network_simplex(G) + assert flowCost == 0 + assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}} + flowCost, H = nx.capacity_scaling(G) + assert flowCost == 0 + assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}} + + def test_exceptions(self): + G = nx.Graph() + pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G) + pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G) + G = nx.MultiGraph() + pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G) + pytest.raises(nx.NetworkXNotImplemented, nx.capacity_scaling, G) + G = nx.DiGraph() + pytest.raises(nx.NetworkXError, nx.network_simplex, G) + # pytest.raises(nx.NetworkXError, nx.capacity_scaling, G) + G.add_node(0, demand=float("inf")) + pytest.raises(nx.NetworkXError, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + G.nodes[0]["demand"] = 0 + G.add_node(1, demand=0) + G.add_edge(0, 1, weight=-float("inf")) + pytest.raises(nx.NetworkXError, nx.network_simplex, G) + pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + G[0][1]["weight"] = 0 + G.add_edge(0, 0, weight=float("inf")) + pytest.raises(nx.NetworkXError, nx.network_simplex, G) + # pytest.raises(nx.NetworkXError, nx.capacity_scaling, G) + G[0][0]["weight"] = 0 + G[0][1]["capacity"] = -1 + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + # pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + G[0][1]["capacity"] = 0 + G[0][0]["capacity"] = -1 + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + # pytest.raises(nx.NetworkXUnfeasible, nx.capacity_scaling, G) + + def test_large(self): + fname = ( + importlib.resources.files("networkx.algorithms.flow.tests") + / "netgen-2.gpickle.bz2" + ) + with bz2.BZ2File(fname, "rb") as f: + G = pickle.load(f) + flowCost, flowDict = nx.network_simplex(G) + assert 6749969302 == flowCost + assert 6749969302 == nx.cost_of_flow(G, flowDict) + flowCost, flowDict = nx.capacity_scaling(G) + assert 6749969302 == flowCost + assert 6749969302 == nx.cost_of_flow(G, flowDict) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_networksimplex.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_networksimplex.py new file mode 100644 index 0000000000000000000000000000000000000000..9a37fc6e9a41579fb9555b27710f3e5298d9d321 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/flow/tests/test_networksimplex.py @@ -0,0 +1,481 @@ +import bz2 +import importlib.resources +import pickle + +import pytest + +import networkx as nx + + +@pytest.fixture +def simple_flow_graph(): + G = nx.DiGraph() + G.add_node("a", demand=0) + G.add_node("b", demand=-5) + G.add_node("c", demand=50000000) + G.add_node("d", demand=-49999995) + G.add_edge("a", "b", weight=3, capacity=4) + G.add_edge("a", "c", weight=6, capacity=10) + G.add_edge("b", "d", weight=1, capacity=9) + G.add_edge("c", "d", weight=2, capacity=5) + return G + + +@pytest.fixture +def simple_no_flow_graph(): + G = nx.DiGraph() + G.add_node("s", demand=-5) + G.add_node("t", demand=5) + G.add_edge("s", "a", weight=1, capacity=3) + G.add_edge("a", "b", weight=3) + G.add_edge("a", "c", weight=-6) + G.add_edge("b", "d", weight=1) + G.add_edge("c", "d", weight=-2) + G.add_edge("d", "t", weight=1, capacity=3) + return G + + +def get_flowcost_from_flowdict(G, flowDict): + """Returns flow cost calculated from flow dictionary""" + flowCost = 0 + for u in flowDict: + for v in flowDict[u]: + flowCost += flowDict[u][v] * G[u][v]["weight"] + return flowCost + + +def test_infinite_demand_raise(simple_flow_graph): + G = simple_flow_graph + inf = float("inf") + node_name = "a" + nx.set_node_attributes(G, {node_name: {"demand": inf}}) + with pytest.raises( + nx.NetworkXError, + match=f"node '{node_name}' has infinite demand", + ): + nx.network_simplex(G) + + +def test_neg_infinite_demand_raise(simple_flow_graph): + G = simple_flow_graph + inf = float("inf") + node_name = "a" + nx.set_node_attributes(G, {node_name: {"demand": -inf}}) + with pytest.raises( + nx.NetworkXError, + match=f"node '{node_name}' has infinite demand", + ): + nx.network_simplex(G) + + +def test_infinite_weight_raise(simple_flow_graph): + G = simple_flow_graph + inf = float("inf") + nx.set_edge_attributes( + G, {("a", "b"): {"weight": inf}, ("b", "d"): {"weight": inf}} + ) + with pytest.raises( + nx.NetworkXError, + match=r"edge .* has infinite weight", + ): + nx.network_simplex(G) + + +def test_nonzero_net_demand_raise(simple_flow_graph): + G = simple_flow_graph + nx.set_node_attributes(G, {"b": {"demand": -4}}) + with pytest.raises( + nx.NetworkXUnfeasible, + match="total node demand is not zero", + ): + nx.network_simplex(G) + + +def test_negative_capacity_raise(simple_flow_graph): + G = simple_flow_graph + nx.set_edge_attributes(G, {("a", "b"): {"weight": 1}, ("b", "d"): {"capacity": -9}}) + with pytest.raises( + nx.NetworkXUnfeasible, + match=r"edge .* has negative capacity", + ): + nx.network_simplex(G) + + +def test_no_flow_satisfying_demands(simple_no_flow_graph): + G = simple_no_flow_graph + with pytest.raises( + nx.NetworkXUnfeasible, + match="no flow satisfies all node demands", + ): + nx.network_simplex(G) + + +def test_sum_demands_not_zero(simple_no_flow_graph): + G = simple_no_flow_graph + nx.set_node_attributes(G, {"t": {"demand": 4}}) + with pytest.raises( + nx.NetworkXUnfeasible, + match="total node demand is not zero", + ): + nx.network_simplex(G) + + +def test_google_or_tools_example(): + """ + https://developers.google.com/optimization/flow/mincostflow + """ + G = nx.DiGraph() + start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4] + end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2] + capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5] + unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3] + supplies = [20, 0, 0, -5, -15] + answer = 150 + + for i in range(len(supplies)): + G.add_node(i, demand=(-1) * supplies[i]) # supplies are negative of demand + + for i in range(len(start_nodes)): + G.add_edge( + start_nodes[i], end_nodes[i], weight=unit_costs[i], capacity=capacities[i] + ) + + flowCost, flowDict = nx.network_simplex(G) + assert flowCost == answer + assert flowCost == get_flowcost_from_flowdict(G, flowDict) + + +def test_google_or_tools_example2(): + """ + https://developers.google.com/optimization/flow/mincostflow + """ + G = nx.DiGraph() + start_nodes = [0, 0, 1, 1, 1, 2, 2, 3, 4, 3] + end_nodes = [1, 2, 2, 3, 4, 3, 4, 4, 2, 5] + capacities = [15, 8, 20, 4, 10, 15, 4, 20, 5, 10] + unit_costs = [4, 4, 2, 2, 6, 1, 3, 2, 3, 4] + supplies = [23, 0, 0, -5, -15, -3] + answer = 183 + + for i in range(len(supplies)): + G.add_node(i, demand=(-1) * supplies[i]) # supplies are negative of demand + + for i in range(len(start_nodes)): + G.add_edge( + start_nodes[i], end_nodes[i], weight=unit_costs[i], capacity=capacities[i] + ) + + flowCost, flowDict = nx.network_simplex(G) + assert flowCost == answer + assert flowCost == get_flowcost_from_flowdict(G, flowDict) + + +def test_large(): + fname = ( + importlib.resources.files("networkx.algorithms.flow.tests") + / "netgen-2.gpickle.bz2" + ) + + with bz2.BZ2File(fname, "rb") as f: + G = pickle.load(f) + flowCost, flowDict = nx.network_simplex(G) + assert 6749969302 == flowCost + assert 6749969302 == nx.cost_of_flow(G, flowDict) + + +def test_simple_digraph(): + G = nx.DiGraph() + G.add_node("a", demand=-5) + G.add_node("d", demand=5) + G.add_edge("a", "b", weight=3, capacity=4) + G.add_edge("a", "c", weight=6, capacity=10) + G.add_edge("b", "d", weight=1, capacity=9) + G.add_edge("c", "d", weight=2, capacity=5) + flowCost, H = nx.network_simplex(G) + soln = {"a": {"b": 4, "c": 1}, "b": {"d": 4}, "c": {"d": 1}, "d": {}} + assert flowCost == 24 + assert nx.min_cost_flow_cost(G) == 24 + assert H == soln + + +def test_negcycle_infcap(): + G = nx.DiGraph() + G.add_node("s", demand=-5) + G.add_node("t", demand=5) + G.add_edge("s", "a", weight=1, capacity=3) + G.add_edge("a", "b", weight=3) + G.add_edge("c", "a", weight=-6) + G.add_edge("b", "d", weight=1) + G.add_edge("d", "c", weight=-2) + G.add_edge("d", "t", weight=1, capacity=3) + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + + +def test_transshipment(): + G = nx.DiGraph() + G.add_node("a", demand=1) + G.add_node("b", demand=-2) + G.add_node("c", demand=-2) + G.add_node("d", demand=3) + G.add_node("e", demand=-4) + G.add_node("f", demand=-4) + G.add_node("g", demand=3) + G.add_node("h", demand=2) + G.add_node("r", demand=3) + G.add_edge("a", "c", weight=3) + G.add_edge("r", "a", weight=2) + G.add_edge("b", "a", weight=9) + G.add_edge("r", "c", weight=0) + G.add_edge("b", "r", weight=-6) + G.add_edge("c", "d", weight=5) + G.add_edge("e", "r", weight=4) + G.add_edge("e", "f", weight=3) + G.add_edge("h", "b", weight=4) + G.add_edge("f", "d", weight=7) + G.add_edge("f", "h", weight=12) + G.add_edge("g", "d", weight=12) + G.add_edge("f", "g", weight=-1) + G.add_edge("h", "g", weight=-10) + flowCost, H = nx.network_simplex(G) + soln = { + "a": {"c": 0}, + "b": {"a": 0, "r": 2}, + "c": {"d": 3}, + "d": {}, + "e": {"r": 3, "f": 1}, + "f": {"d": 0, "g": 3, "h": 2}, + "g": {"d": 0}, + "h": {"b": 0, "g": 0}, + "r": {"a": 1, "c": 1}, + } + assert flowCost == 41 + assert H == soln + + +def test_digraph1(): + # From Bradley, S. P., Hax, A. C. and Magnanti, T. L. Applied + # Mathematical Programming. Addison-Wesley, 1977. + G = nx.DiGraph() + G.add_node(1, demand=-20) + G.add_node(4, demand=5) + G.add_node(5, demand=15) + G.add_edges_from( + [ + (1, 2, {"capacity": 15, "weight": 4}), + (1, 3, {"capacity": 8, "weight": 4}), + (2, 3, {"weight": 2}), + (2, 4, {"capacity": 4, "weight": 2}), + (2, 5, {"capacity": 10, "weight": 6}), + (3, 4, {"capacity": 15, "weight": 1}), + (3, 5, {"capacity": 5, "weight": 3}), + (4, 5, {"weight": 2}), + (5, 3, {"capacity": 4, "weight": 1}), + ] + ) + flowCost, H = nx.network_simplex(G) + soln = { + 1: {2: 12, 3: 8}, + 2: {3: 8, 4: 4, 5: 0}, + 3: {4: 11, 5: 5}, + 4: {5: 10}, + 5: {3: 0}, + } + assert flowCost == 150 + assert nx.min_cost_flow_cost(G) == 150 + assert H == soln + + +def test_zero_capacity_edges(): + """Address issue raised in ticket #617 by arv.""" + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2, {"capacity": 1, "weight": 1}), + (1, 5, {"capacity": 1, "weight": 1}), + (2, 3, {"capacity": 0, "weight": 1}), + (2, 5, {"capacity": 1, "weight": 1}), + (5, 3, {"capacity": 2, "weight": 1}), + (5, 4, {"capacity": 0, "weight": 1}), + (3, 4, {"capacity": 2, "weight": 1}), + ] + ) + G.nodes[1]["demand"] = -1 + G.nodes[2]["demand"] = -1 + G.nodes[4]["demand"] = 2 + + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0, 5: 1}, 2: {3: 0, 5: 1}, 3: {4: 2}, 4: {}, 5: {3: 2, 4: 0}} + assert flowCost == 6 + assert nx.min_cost_flow_cost(G) == 6 + assert H == soln + + +def test_digon(): + """Check if digons are handled properly. Taken from ticket + #618 by arv.""" + nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})] + edges = [ + (1, 2, {"capacity": 3, "weight": 600000}), + (2, 1, {"capacity": 2, "weight": 0}), + (2, 3, {"capacity": 5, "weight": 714285}), + (3, 2, {"capacity": 2, "weight": 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + flowCost, H = nx.network_simplex(G) + soln = {1: {2: 0}, 2: {1: 0, 3: 4}, 3: {2: 0}} + assert flowCost == 2857140 + + +def test_deadend(): + """Check if one-node cycles are handled properly. Taken from ticket + #2906 from @sshraven.""" + G = nx.DiGraph() + + G.add_nodes_from(range(5), demand=0) + G.nodes[4]["demand"] = -13 + G.nodes[3]["demand"] = 13 + + G.add_edges_from([(0, 2), (0, 3), (2, 1)], capacity=20, weight=0.1) + pytest.raises(nx.NetworkXUnfeasible, nx.network_simplex, G) + + +def test_infinite_capacity_neg_digon(): + """An infinite capacity negative cost digon results in an unbounded + instance.""" + nodes = [(1, {}), (2, {"demand": -4}), (3, {"demand": 4})] + edges = [ + (1, 2, {"weight": -600}), + (2, 1, {"weight": 0}), + (2, 3, {"capacity": 5, "weight": 714285}), + (3, 2, {"capacity": 2, "weight": 0}), + ] + G = nx.DiGraph(edges) + G.add_nodes_from(nodes) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + + +def test_multidigraph(): + """Multidigraphs are acceptable.""" + G = nx.MultiDiGraph() + G.add_weighted_edges_from([(1, 2, 1), (2, 3, 2)], weight="capacity") + flowCost, H = nx.network_simplex(G) + assert flowCost == 0 + assert H == {1: {2: {0: 0}}, 2: {3: {0: 0}}, 3: {}} + + +def test_negative_selfloops(): + """Negative selfloops should cause an exception if uncapacitated and + always be saturated otherwise. + """ + G = nx.DiGraph() + G.add_edge(1, 1, weight=-1) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + + G[1][1]["capacity"] = 2 + flowCost, H = nx.network_simplex(G) + assert flowCost == -2 + assert H == {1: {1: 2}} + + G = nx.MultiDiGraph() + G.add_edge(1, 1, "x", weight=-1) + G.add_edge(1, 1, "y", weight=1) + pytest.raises(nx.NetworkXUnbounded, nx.network_simplex, G) + + G[1][1]["x"]["capacity"] = 2 + flowCost, H = nx.network_simplex(G) + assert flowCost == -2 + assert H == {1: {1: {"x": 2, "y": 0}}} + + +def test_bone_shaped(): + # From #1283 + G = nx.DiGraph() + G.add_node(0, demand=-4) + G.add_node(1, demand=2) + G.add_node(2, demand=2) + G.add_node(3, demand=4) + G.add_node(4, demand=-2) + G.add_node(5, demand=-2) + G.add_edge(0, 1, capacity=4) + G.add_edge(0, 2, capacity=4) + G.add_edge(4, 3, capacity=4) + G.add_edge(5, 3, capacity=4) + G.add_edge(0, 3, capacity=0) + flowCost, H = nx.network_simplex(G) + assert flowCost == 0 + assert H == {0: {1: 2, 2: 2, 3: 0}, 1: {}, 2: {}, 3: {}, 4: {3: 2}, 5: {3: 2}} + + +def test_graphs_type_exceptions(): + G = nx.Graph() + pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G) + G = nx.MultiGraph() + pytest.raises(nx.NetworkXNotImplemented, nx.network_simplex, G) + G = nx.DiGraph() + pytest.raises(nx.NetworkXError, nx.network_simplex, G) + + +@pytest.fixture() +def faux_inf_example(): + """Base test graph for probing faux_infinity bound. See gh-7562""" + G = nx.DiGraph() + + # Add nodes with demands + G.add_node("s0", demand=-4) + G.add_node("s1", demand=-4) + G.add_node("ns", demand=0) + G.add_node("nc", demand=0) + G.add_node("c0", demand=4) + G.add_node("c1", demand=4) + + # Uniformly weighted edges + G.add_edge("s0", "ns", weight=1) + G.add_edge("s1", "ns", weight=1) + G.add_edge("ns", "nc", weight=1) + G.add_edge("nc", "c0", weight=1) + G.add_edge("nc", "c1", weight=1) + + return G + + +@pytest.mark.parametrize("large_capacity", [True, False]) +@pytest.mark.parametrize("large_demand", [True, False]) +@pytest.mark.parametrize("large_weight", [True, False]) +def test_network_simplex_faux_infinity( + faux_inf_example, large_capacity, large_demand, large_weight +): + """network_simplex should not raise an exception as a result of faux_infinity + for these cases. See gh-7562""" + G = faux_inf_example + lv = 1_000_000_000 + + # Modify the base graph with combinations of large values for capacity, + # demand, and weight to probe faux_inifity + if large_capacity: + G["s0"]["ns"]["capacity"] = lv + if large_demand: + G.nodes["s0"]["demand"] = -lv + G.nodes["c1"]["demand"] = lv + if large_weight: + G["s1"]["ns"]["weight"] = lv + + # Execute without raising + fc, fd = nx.network_simplex(G) + + +def test_network_simplex_unbounded_flow(): + G = nx.DiGraph() + # Add nodes + G.add_node("A") + G.add_node("B") + G.add_node("C") + + # Add edges forming a negative cycle + G.add_weighted_edges_from([("A", "B", -5), ("B", "C", -5), ("C", "A", -5)]) + + with pytest.raises( + nx.NetworkXUnbounded, + match="negative cycle with infinite capacity found", + ): + nx.network_simplex(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca4cb15967d0fae6e5a683db58961773ecd1ece2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/ismags.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/ismags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f471dbee3fc14b6a5425ee903c30b67558b9c12 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/ismags.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorph.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f673faf987bc89c395ae1bcfc53b718b533ae12 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorph.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorphvf2.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorphvf2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7c6ecd26e8e08f3530be8d68f67895ad77a4757 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/isomorphvf2.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/matchhelpers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/matchhelpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fde99f873933b9eb7a9a916e8c5c35e85ca872d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/matchhelpers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/temporalisomorphvf2.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/temporalisomorphvf2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..322ba7b76c58a3203339f3719f49285c4d1001a8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/temporalisomorphvf2.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/tree_isomorphism.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/tree_isomorphism.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..478155c8bcccd2dac6729508a11645ba01718d55 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/tree_isomorphism.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2pp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2pp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6387cad58034edaac04ea35028a58086b422f29 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2pp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2userfunc.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2userfunc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b739694e94bfcdb9d526a390bf5834446c96b42 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/__pycache__/vf2userfunc.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b0c4ccf25e88b79a7ef90798a89e0c09e7ad51c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_ismags.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_ismags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84f0d9c8cb8b5eafe1968ffd29536ff2179d9129 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_ismags.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphism.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphism.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ec1ba38f034638bab024626e748dd5d5175f26e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphism.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphvf2.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphvf2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a445ce433f36c53f853d78be6fce85fc0576e60b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_isomorphvf2.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_match_helpers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_match_helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46fbb1cb4865dd7217249db66e4943435609e7fd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_match_helpers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_temporalisomorphvf2.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_temporalisomorphvf2.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21ec61db2402c7d1ad8d4b111a45af2f3f2b8967 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_temporalisomorphvf2.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_tree_isomorphism.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_tree_isomorphism.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c084475c1dd419f0f5a847c01d02c1df17e7236 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_tree_isomorphism.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6faddc7d24b2e2eb5a37e0f33fde87ebf3cef1d2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp_helpers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp_helpers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffb353e3942fc0575091af134566186620d52f6a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2pp_helpers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2userfunc.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2userfunc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..021479513e42ab5a6e48cf0b57ae441775702848 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/__pycache__/test_vf2userfunc.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 new file mode 100644 index 0000000000000000000000000000000000000000..dac54f0038c70e2d359ffa68de3c7641b46db21a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 new file mode 100644 index 0000000000000000000000000000000000000000..6c6af680031b4f30fc6da946d0344b5c27e5f05e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 new file mode 100644 index 0000000000000000000000000000000000000000..60c3a3ce1bdb54a61ead043f0adaf24b1fe24e93 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 new file mode 100644 index 0000000000000000000000000000000000000000..0236872094d4c73b0bb132165ce3ec4d1054f5f5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_ismags.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_ismags.py new file mode 100644 index 0000000000000000000000000000000000000000..3b7a8c4930e7869189297ca8bc2c8d87b05703e2 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_ismags.py @@ -0,0 +1,719 @@ +""" +Tests for ISMAGS isomorphism algorithm. +""" + +import random + +import pytest + +import networkx as nx +from networkx.algorithms import isomorphism as iso + +graph_classes = [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph] + + +def _matches_to_sets(matches): + """ + Helper function to facilitate comparing collections of dictionaries in + which order does not matter. + """ + return {frozenset(m.items()) for m in matches} + + +graph_examples = [ + # node_data, edge_data, [id used in name for the test] + pytest.param([0, 1, 2, 3], [(0, 0)], id="isolated-nodes-and-selfloops"), + pytest.param([], nx.star_graph(3).edges, id="3-star"), + pytest.param( + # 6-cycle with 2-paths stuck onto nodes 0, 2, 4 (stretched symmetry) + [], + [ + (0, 1), + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 0), + (0, 6), + (6, 7), + (2, 8), + (8, 9), + (4, 10), + (10, 11), + ], + id="sun:6-cycle-with-2-path-rays", + ), + # 0-1-2-3-5 + # / \ + # 4 6 + pytest.param([], [(0, 1), (1, 2), (1, 4), (2, 3), (3, 5), (3, 6)], id="tree"), + pytest.param([], nx.petersen_graph().edges, id="petersen_graph"), + # Example Fig 3 from Houbraken, et al (ISMAGS paper) + pytest.param( + [], nx.cycle_graph([1, 2, 4, 3]).edges, id="houbraken-ismags-paper-fig3" + ), + pytest.param( + # path with node labels + [ + (0, {"name": "a"}), + (1, {"name": "a"}), + (2, {"name": "b"}), + (3, {"name": "b"}), + (4, {"name": "a"}), + (5, {"name": "a"}), + ], + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)], + id="path-with-node-labels", + ), + pytest.param( + # 5 - 4 \ / 12 - 13 + # 0 - 3 + # 9 - 8 / \ 16 - 17 + # Assume 0 and 3 are coupled and no longer equivalent. + # Coupling node 4 to 8 means that 5 and 9 + # are no longer equivalent, pushing them in their own partitions. + # So, [{5}, {9}] is no longer considered equivalent to {13, 17}. + # Minimal example with this trait. Adding all permutations of + # same-size parts at each step finds the symmetry. + [], + [ + (0, 3), + (3, 0), # added to provide symmetry for DiGraphs + (0, 4), + (4, 5), + (0, 8), + (8, 9), + (3, 12), + (12, 13), + (3, 16), + (16, 17), + ], + id="gh8055-tricky-case", + ), + pytest.param( + [], nx.path_graph([1, 2, 3, "a", "b", "c"]).edges, id="unsortable-nodes" + ), + # Example from Katebi, 2012, Fig 1-3. + # Node order specified to (almost) match their DFS order + pytest.param( + [3, 5, 6, 4, 2, 1, 0], + set(nx.cycle_graph(4).edges) | set(nx.cycle_graph(range(4, 7)).edges), + id="katebi-paper-fig2", + ), + pytest.param( + [], [(0, 1), (1, 2), (2, 3), (3, 6), (2, 4), (4, 5)], id="len-2-rays-tri-star" + ), + # Example of refining permutations with two different length parts at the same time. + # Underlying shape is a 4-cycle and 2-path. Multiedges make all nodes degree-3 + # Full simple graph is then obtained by extending each edge as a path thru 1 node. + # 0 + # Underlying // \ 4 When 0->0 coupling occurs, + # MultiGraph 1 3 \\\ refining {1, 2, 3, 4, 5} + # \ // 5 refined parts [{1}, {3}, {2, 4, 5}] + # 2 with different parts having different lengths. + # 0 + # /|\ 4 + # 6 7 8 /|\ + # Full: |/ | / | \ + # 1 3 12 13 14 + # | / \ \ | / + # 9 10 11 \|/ + # \ | / 5 + # 2 + # Nodes 0-5 are the degree-3 nodes. + # Nodes 6-14 are degree 2 nodes on paths between the degree-3 nodes. + pytest.param( + [], + [ + (0, 6), + (0, 7), + (0, 8), + (1, 6), + (1, 7), + (1, 9), + (2, 9), + (2, 10), + (2, 11), + (3, 8), + (3, 10), + (3, 11), + (4, 12), + (4, 13), + (4, 14), + (5, 12), + (5, 13), + (5, 14), + ], + id="refining-parts-finds-different-lengths", + ), + # Underlying structure from previous example + pytest.param( + [], + [(0, 1), (0, 1), (1, 2), (2, 3), (2, 3), (3, 0), (4, 5), (4, 5), (4, 5)], + id="basic-structure-for-refining-parts-test", + ), +] + + +@pytest.mark.parametrize("graph_constructor", graph_classes) +class TestSelfIsomorphism: + @pytest.mark.parametrize(["node_data", "edge_data"], graph_examples) + def test_self_isomorphism(self, graph_constructor, node_data, edge_data): + """ + For some small, symmetric graphs, make sure that 1) they are isomorphic + to themselves, and 2) that only the identity mapping is found. + """ + graph = graph_constructor() + graph.add_nodes_from(node_data) + graph.add_edges_from(edge_data) + + ismags = iso.ISMAGS( + graph, graph, node_match=iso.categorical_node_match("name", None) + ) + assert ismags.is_isomorphic() + assert ismags.is_isomorphic(symmetry=True) + assert ismags.subgraph_is_isomorphic() + ismags_answer = list(ismags.subgraph_isomorphisms_iter(symmetry=True)) + assert ismags_answer == [{n: n for n in graph.nodes}] + + +class TestSubgraphIsomorphism: + def test_isomorphism_4_sun(self): + g1 = nx.cycle_graph(4) + g2 = nx.cycle_graph(4) + g2.add_edges_from(list(zip(g2, range(4, 8)))) + ismags = iso.ISMAGS(g2, g1) + assert list(ismags.subgraph_isomorphisms_iter(symmetry=True)) == [ + {n: n for n in g1.nodes} + ] + assert sum(1 for _ in ismags.subgraph_isomorphisms_iter(symmetry=False)) == 8 + + def test_isomorphism_path_in_tristar(self): + g1 = nx.path_graph(3) + + g2 = g1.copy() + g2.add_edge(1, 3) + + ismags = iso.ISMAGS(g2, g1) + matches = ismags.subgraph_isomorphisms_iter(symmetry=True) + expected_symmetric = [ + {0: 0, 1: 1, 2: 2}, + {0: 0, 1: 1, 3: 2}, + {2: 0, 1: 1, 3: 2}, + ] + assert _matches_to_sets(matches) == _matches_to_sets(expected_symmetric) + + matches = ismags.subgraph_isomorphisms_iter(symmetry=False) + expected_asymmetric = [ + {0: 2, 1: 1, 2: 0}, + {0: 2, 1: 1, 3: 0}, + {2: 2, 1: 1, 3: 0}, + ] + assert _matches_to_sets(matches) == _matches_to_sets( + expected_symmetric + expected_asymmetric + ) + + def test_labeled_nodes(self): + g1 = nx.cycle_graph(3) + g1.nodes[1]["attr"] = True + + g2 = g1.copy() + g2.add_edge(1, 3) + ismags = iso.ISMAGS(g2, g1, node_match=lambda x, y: x == y) + matches = ismags.subgraph_isomorphisms_iter(symmetry=True) + expected_symmetric = [{0: 0, 1: 1, 2: 2}] + assert _matches_to_sets(matches) == _matches_to_sets(expected_symmetric) + + matches = ismags.subgraph_isomorphisms_iter(symmetry=False) + expected_asymmetric = [{0: 2, 1: 1, 2: 0}] + assert _matches_to_sets(matches) == _matches_to_sets( + expected_symmetric + expected_asymmetric + ) + + def test_labeled_edges(self): + g1 = nx.Graph() + nx.add_cycle(g1, range(3)) + g1.edges[1, 2]["attr"] = True + + g2 = g1.copy() + g2.add_edge(1, 3) + ismags = iso.ISMAGS(g2, g1, edge_match=lambda x, y: x == y) + matches = ismags.subgraph_isomorphisms_iter(symmetry=True) + expected_symmetric = [{0: 0, 1: 1, 2: 2}] + assert _matches_to_sets(matches) == _matches_to_sets(expected_symmetric) + + matches = ismags.subgraph_isomorphisms_iter(symmetry=False) + expected_asymmetric = [{1: 2, 0: 0, 2: 1}] + assert _matches_to_sets(matches) == _matches_to_sets( + expected_symmetric + expected_asymmetric + ) + + def test_exceptions_for_bad_match_functions(self): + def non_transitive_match(attrs1, attrs2): + return abs(attrs1["freq"] - attrs2["freq"]) <= 1 + + def simple_non_commutative_match(attrs1, attrs2): + return attrs1["freq"] == 1 + attrs2["freq"] + + def non_commutative_match(attrs1, attrs2): + # red matches red and green + # green and blue only match themselves + if attrs2["color"] == "red": + return attrs2["color"] in {"red", "green"} + else: + return attrs1["color"] == attrs2["color"] + + G1 = nx.Graph() + G1.add_node(0, color="red", freq=0) + G1.add_node(1, color="red", freq=1) + G1.add_node(2, color="blue", freq=2) + + G2 = nx.Graph() + G2.add_node("A", color="red", freq=0) + G2.add_node("B", color="green", freq=1) + G2.add_node("C", color="blue", freq=2) + + with pytest.raises(nx.NetworkXError, match="\nInvalid partition"): + iso.ISMAGS(G1, G2, node_match=non_transitive_match) + + with pytest.raises(nx.NetworkXError, match="\nInvalid partition"): + iso.ISMAGS(G1, G2, node_match=simple_non_commutative_match) + + with pytest.raises(nx.NetworkXError, match="\nInvalid partition"): + iso.ISMAGS(G1, G2, node_match=non_commutative_match) + + +def test_noncomparable_nodes(): + node1 = object() + node2 = object() + node3 = object() + + # Graph + G = nx.path_graph([node1, node2, node3]) + gm = iso.ISMAGS(G, G) + assert gm.is_isomorphic() + assert gm.subgraph_is_isomorphic() + + # DiGraph + G = nx.path_graph([node1, node2, node3], create_using=nx.DiGraph) + H = nx.path_graph([node3, node2, node1], create_using=nx.DiGraph) + dgm = iso.ISMAGS(G, H) + assert dgm.is_isomorphic() + assert dgm.is_isomorphic(symmetry=True) + assert dgm.subgraph_is_isomorphic() + + +@pytest.mark.parametrize("graph_constructor", graph_classes) +def test_selfloop(graph_constructor): + # Simple test for graphs with selfloops + g1 = graph_constructor([(0, 1), (0, 2), (1, 2), (1, 3), (2, 2), (2, 4)]) + nodes = range(5) + rng = random.Random(42) + + for _ in range(3): + new_nodes = list(nodes) + rng.shuffle(new_nodes) + d = dict(zip(nodes, new_nodes)) + g2 = nx.relabel_nodes(g1, d) + assert iso.ISMAGS(g1, g2).is_isomorphic() + + +class TestWikipediaExample: + # example in wikipedia is g1a and g2b + # 1 have letter nodes, 2 have number nodes + # b have some edges reversed vs a (undirected still isomorphic) + # reversed edges marked with comment `#` + # isomorphism = {'a': 1, 'g': 2, 'b': 3, 'c': 6, 'h': 4, 'i': 5, 'j': 7, 'd': 8} + + # Nodes 'a', 'b', 'c' and 'd' form a column. + # Nodes 'g', 'h', 'i' and 'j' form a column. + g1a_edges = [ + ["a", "g"], + ["a", "h"], # edge direction swapped from g1b + ["a", "i"], + ["b", "g"], # edge direction swapped from g1b + ["b", "h"], + ["b", "j"], + ["c", "g"], # edge direction swapped from g1b + ["c", "i"], # edge direction swapped from g1b + ["c", "j"], + ["d", "h"], # edge direction swapped from g1b + ["d", "i"], + ["d", "j"], # edge direction swapped from g1b + ] + + g1b_edges = [ + ["a", "g"], + ["h", "a"], # edge direction swapped from g1a + ["a", "i"], + ["g", "b"], # edge direction swapped from g1a + ["b", "h"], + ["b", "j"], + ["g", "c"], # edge direction swapped from g1a + ["i", "c"], # edge direction swapped from g1a + ["c", "j"], + ["h", "d"], # edge direction swapped from g1a + ["d", "i"], + ["j", "d"], # edge direction swapped from g1a + ] + + g2b_edges = [ + [1, 2], + [1, 4], # edge direction swapped from g2a + [1, 5], + [3, 2], # edge direction swapped from g2a + [3, 4], + [3, 7], + [6, 2], # edge direction swapped from g2a + [6, 5], # edge direction swapped from g2a + [6, 7], + [8, 4], # edge direction swapped from g2a + [8, 5], + [8, 7], # edge direction swapped from g2a + ] + + # Nodes 1,2,3,4 form the clockwise corners of a large square. + # Nodes 5,6,7,8 form the clockwise corners of a small square + g2a_edges = [ + [1, 2], + [4, 1], # edge direction swapped from g2b + [1, 5], + [2, 3], # edge direction swapped from g2b + [3, 4], + [3, 7], + [2, 6], # edge direction swapped from g2b + [5, 6], # edge direction swapped from g2b + [6, 7], + [4, 8], # edge direction swapped from g2b + [8, 5], + [7, 8], # edge direction swapped from g2b + ] + + @pytest.mark.parametrize("graph_constructor", [nx.Graph, nx.MultiGraph]) + def test_graph(self, graph_constructor): + g1a = graph_constructor(self.g1a_edges) + g1b = graph_constructor(self.g1b_edges) + g2a = graph_constructor(self.g2a_edges) + g2b = graph_constructor(self.g2b_edges) + assert iso.ISMAGS(g1a, g1b).is_isomorphic() + assert iso.ISMAGS(g1a, g2a).is_isomorphic() + assert iso.ISMAGS(g1a, g2b).is_isomorphic() + + assert iso.ISMAGS(g1a, nx.path_graph(range(5))).subgraph_is_isomorphic() + assert not iso.ISMAGS(g1a, nx.path_graph(range(6))).subgraph_is_isomorphic() + + @pytest.mark.parametrize("graph_constructor", [nx.DiGraph, nx.MultiDiGraph]) + def test_digraph(self, graph_constructor): + g1a = graph_constructor(self.g1a_edges) + g1b = graph_constructor(self.g1b_edges) + g2a = graph_constructor(self.g2a_edges) + g2b = graph_constructor(self.g2b_edges) + assert iso.ISMAGS(g1a, g2b).is_isomorphic() + assert iso.ISMAGS(g1b, g2a).is_isomorphic() + assert not iso.ISMAGS(g1a, g1b).is_isomorphic() + assert not iso.ISMAGS(g2a, g2b).is_isomorphic() + assert not iso.ISMAGS(g1a, g2a).is_isomorphic() + assert not iso.ISMAGS(g1b, g2b).is_isomorphic() + + P2 = nx.path_graph(range(2), create_using=graph_constructor) + assert iso.ISMAGS(g1a, P2).subgraph_is_isomorphic() + P3 = nx.path_graph(range(3), create_using=graph_constructor) + assert not iso.ISMAGS(g1a, P3).subgraph_is_isomorphic() + + +class TestLargestCommonSubgraph: + def test_mcis(self): + # Example graphs from DOI: 10.1002/spe.588 + graph1 = nx.Graph() + graph1.add_edges_from([(1, 2), (2, 3), (2, 4), (3, 4), (4, 5)]) + graph1.nodes[1]["color"] = 0 + + graph2 = nx.Graph() + graph2.add_edges_from( + [(1, 2), (2, 3), (2, 4), (3, 4), (3, 5), (5, 6), (5, 7), (6, 7)] + ) + graph2.nodes[1]["color"] = 1 + graph2.nodes[6]["color"] = 2 + graph2.nodes[7]["color"] = 2 + + ismags = iso.ISMAGS( + graph1, graph2, node_match=iso.categorical_node_match("color", None) + ) + assert list(ismags.subgraph_isomorphisms_iter(symmetry=True)) == [] + assert list(ismags.subgraph_isomorphisms_iter(symmetry=False)) == [] + found_mcis = _matches_to_sets(ismags.largest_common_subgraph()) + expected = _matches_to_sets( + [{2: 2, 3: 4, 4: 3, 5: 5}, {2: 4, 3: 2, 4: 3, 5: 5}] + ) + assert expected == found_mcis + + ismags = iso.ISMAGS( + graph2, graph1, node_match=iso.categorical_node_match("color", None) + ) + assert list(ismags.subgraph_isomorphisms_iter(symmetry=True)) == [] + assert list(ismags.subgraph_isomorphisms_iter(symmetry=False)) == [] + found_mcis = _matches_to_sets(ismags.largest_common_subgraph()) + # Same answer, but reversed. + expected = _matches_to_sets( + [{2: 2, 3: 4, 4: 3, 5: 5}, {4: 2, 2: 3, 3: 4, 5: 5}] + ) + assert expected == found_mcis + + def test_symmetry_mcis(self): + graph1 = nx.Graph() + nx.add_path(graph1, range(4)) + + graph2 = nx.Graph() + nx.add_path(graph2, range(3)) + graph2.add_edge(1, 3) + + # Only the symmetry of graph2 is taken into account here. + ismags1 = iso.ISMAGS( + graph1, graph2, node_match=iso.categorical_node_match("color", None) + ) + assert list(ismags1.subgraph_isomorphisms_iter(symmetry=True)) == [] + found_mcis = _matches_to_sets(ismags1.largest_common_subgraph()) + expected = _matches_to_sets([{0: 0, 1: 1, 2: 2}, {1: 0, 3: 2, 2: 1}]) + assert expected == found_mcis + + # Only the symmetry of graph1 is taken into account here. + ismags2 = iso.ISMAGS( + graph2, graph1, node_match=iso.categorical_node_match("color", None) + ) + assert list(ismags2.subgraph_isomorphisms_iter(symmetry=True)) == [] + found_mcis = _matches_to_sets(ismags2.largest_common_subgraph()) + expected = _matches_to_sets( + [ + {3: 2, 0: 0, 1: 1}, + {2: 0, 0: 2, 1: 1}, + {3: 0, 0: 2, 1: 1}, + {3: 0, 1: 1, 2: 2}, + {0: 0, 1: 1, 2: 2}, + {2: 0, 3: 2, 1: 1}, + ] + ) + + assert expected == found_mcis + + found_mcis1 = _matches_to_sets(ismags1.largest_common_subgraph(symmetry=False)) + found_mcis2 = ismags2.largest_common_subgraph(symmetry=False) + found_mcis2 = [{v: k for k, v in d.items()} for d in found_mcis2] + found_mcis2 = _matches_to_sets(found_mcis2) + + expected = _matches_to_sets( + [ + {3: 2, 1: 3, 2: 1}, + {2: 0, 0: 2, 1: 1}, + {1: 2, 3: 3, 2: 1}, + {3: 0, 1: 3, 2: 1}, + {0: 2, 2: 3, 1: 1}, + {3: 0, 1: 2, 2: 1}, + {2: 0, 0: 3, 1: 1}, + {0: 0, 2: 3, 1: 1}, + {1: 0, 3: 3, 2: 1}, + {1: 0, 3: 2, 2: 1}, + {0: 3, 1: 1, 2: 2}, + {0: 0, 1: 1, 2: 2}, + ] + ) + assert expected == found_mcis1 + assert expected == found_mcis2 + + +def is_isomorphic(G, SG, edge_match=None, node_match=None): + return iso.ISMAGS(G, SG, node_match, edge_match).is_isomorphic() + + +class TestDiGraphISO: + def test_wikipedia_graph(self): + edges1 = [ + (1, 5), + (1, 2), + (1, 4), + (3, 2), + (6, 2), + (3, 4), + (7, 3), + (4, 8), + (5, 8), + (6, 5), + (6, 7), + (7, 8), + ] + mapped = {1: "a", 2: "h", 3: "d", 4: "i", 5: "g", 6: "b", 7: "j", 8: "c"} + + G1 = nx.DiGraph(edges1) + G2 = nx.relabel_nodes(G1, mapped) + + result = next(nx.isomorphism.ISMAGS(G1, G2).find_isomorphisms()) + assert result == mapped + + # Change the direction of an edge + G1.remove_edge(1, 5) + G1.add_edge(5, 1) + result = list(nx.isomorphism.ISMAGS(G1, G2).find_isomorphisms()) + assert result == [] + + def test_non_isomorphic_same_degree_sequence(self): + r""" + G1 G2 + 1-------------2 1-------------2 + | \ | | \ | + | 5-------6 | | 5-------6 | + | | | | | | | | + | 8-------7 | | 8-------7 | + | / | | \ | + 4-------------3 4-------------3 + """ + edges1 = [ + (1, 5), + (1, 2), + (4, 1), + (3, 2), + (3, 4), + (5, 8), + (6, 5), + (6, 7), + (7, 8), + (4, 8), + ] + edges2 = [ + (1, 5), + (1, 2), + (4, 1), + (3, 2), + (3, 4), + (5, 8), + (6, 5), + (6, 7), + (8, 7), + (3, 7), + ] + + G1 = nx.DiGraph(edges1) + G2 = nx.DiGraph(edges2) + assert not is_isomorphic(G1, G2) + + def test_is_isomorphic(self): + G1 = nx.Graph([[1, 2], [1, 3], [1, 5], [2, 3]]) + G2 = nx.Graph([[10, 20], [20, 30], [10, 30], [10, 50]]) + G4 = nx.Graph([[1, 2], [1, 3], [1, 5], [2, 4]]) + assert is_isomorphic(G1, G2) + assert not is_isomorphic(G1, G4) + assert is_isomorphic(G1.to_directed(), G2.to_directed()) + assert not is_isomorphic(G1.to_directed(), G4.to_directed()) + with pytest.raises( + ValueError, match="Directed and undirected graphs cannot be compared." + ): + is_isomorphic(G1.to_directed(), G1) + + +@pytest.mark.parametrize("graph_class", graph_classes) +def test_simple_node_match(graph_class): + g1 = graph_class([(0, 0), (0, 1), (1, 0)]) + g2 = g1.copy() + nm = iso.numerical_node_match("size", 1) + assert is_isomorphic(g1, g2, node_match=nm) + + g2.nodes[0]["size"] = 3 + assert not is_isomorphic(g1, g2, node_match=nm) + + +@pytest.mark.parametrize("graph_class", graph_classes) +def test_simple_node_and_edge_match(graph_class): + g1 = graph_class() + g1.add_weighted_edges_from([(0, 0, 1.2), (0, 1, 1.4), (1, 0, 1.6)]) + g2 = g1.copy() + nm = iso.numerical_node_match("size", 1) + if g1.is_multigraph(): + em = iso.numerical_multiedge_match("weight", 1) + else: + em = iso.numerical_edge_match("weight", 1) + assert is_isomorphic(g1, g2, node_match=nm, edge_match=em) + + g2.nodes[0]["size"] = 3 + assert not is_isomorphic(g1, g2, node_match=nm, edge_match=em) + + g2 = g1.copy() + if g1.is_multigraph(): + g2.edges[0, 1, 0]["weight"] = 2.1 + else: + g2.edges[0, 1]["weight"] = 2.1 + assert not is_isomorphic(g1, g2, node_match=nm, edge_match=em) + + g2 = g1.copy() + g2.nodes[0]["size"] = 3 + if g1.is_multigraph(): + g2.edges[0, 1, 0]["weight"] = 2.1 + else: + g2.edges[0, 1]["weight"] = 2.1 + assert not is_isomorphic(g1, g2, node_match=nm, edge_match=em) + + +@pytest.mark.parametrize("graph_class", graph_classes) +def test_simple_edge_match(graph_class): + # 16 simple tests + w = "weight" + edges = [(0, 0, 1), (0, 0, 1.5), (0, 1, 2), (1, 0, 3)] + g1 = graph_class() + g1.add_weighted_edges_from(edges) + g2 = g1.copy() + if g1.is_multigraph(): + em = iso.numerical_multiedge_match("weight", 1) + else: + em = iso.numerical_edge_match("weight", 1) + assert is_isomorphic(g1, g2, edge_match=em) + + for mod1, mod2 in [(False, True), (True, False), (True, True)]: + # mod1 tests a regular edge weight difference + # mod2 tests a selfloop weight difference + if g1.is_multigraph(): + if mod1: + data1 = {0: {"weight": 10}} + if mod2: + data2 = {0: {"weight": 1}, 1: {"weight": 2.5}} + else: + if mod1: + data1 = {"weight": 10} + if mod2: + data2 = {"weight": 2.5} + + g2 = g1.copy() + if mod1: + if not g1.is_directed(): + g2._adj[1][0] = data1 + g2._adj[0][1] = data1 + else: + g2._succ[1][0] = data1 + g2._pred[0][1] = data1 + if mod2: + if not g1.is_directed(): + g2._adj[0][0] = data2 + else: + g2._succ[0][0] = data2 + g2._pred[0][0] = data2 + + assert not is_isomorphic(g1, g2, edge_match=em) + + +@pytest.mark.parametrize("graph_class", graph_classes) +def test_weightkey(graph_class): + g1 = graph_class() + g2 = graph_class() + if g1.is_multigraph(): + edge_match = iso.numerical_multiedge_match + else: + edge_match = iso.numerical_edge_match + + g1.add_edge("A", "B", weight=1) + g2.add_edge("C", "D", weight=0) + + assert nx.is_isomorphic(g1, g2) + em = edge_match("nonexistent attribute", 1) + assert nx.is_isomorphic(g1, g2, edge_match=em) + em = edge_match("weight", 1) + assert not nx.is_isomorphic(g1, g2, edge_match=em) + + g2 = graph_class() + g2.add_edge("C", "D") + assert nx.is_isomorphic(g1, g2, edge_match=em) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphism.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphism.py new file mode 100644 index 0000000000000000000000000000000000000000..e98e0ee635852b1bc04ae2122ed90cbcc21bbe62 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphism.py @@ -0,0 +1,103 @@ +from functools import partial + +import pytest + +import networkx as nx +from networkx.algorithms import isomorphism as iso + +# Convenience functions for testing that the behavior of `could_be_isomorphic` +# with the "properties" kwarg is equivalent to the corresponding function (i.e. +# nx.fast_could_be_isomorphic or nx.faster_could_be_isomorphic) +fast_cbi = partial(nx.could_be_isomorphic, properties="dt") +faster_cbi = partial(nx.could_be_isomorphic, properties="d") + + +def test_graph_could_be_isomorphic_variants_deprecated(): + G1 = nx.Graph([(1, 2), (1, 3), (1, 5), (2, 3)]) + G2 = nx.Graph([(10, 20), (20, 30), (10, 30), (10, 50)]) + with pytest.deprecated_call(): # graph_could_be_isomorphic + result = nx.isomorphism.isomorph.graph_could_be_isomorphic(G1, G2) + assert nx.could_be_isomorphic(G1, G2) == result + with pytest.deprecated_call(): # fast_graph_could_be_isomorphic + result = nx.isomorphism.isomorph.fast_graph_could_be_isomorphic(G1, G2) + assert nx.fast_could_be_isomorphic(G1, G2) == result + with pytest.deprecated_call(): + result = nx.isomorphism.isomorph.faster_graph_could_be_isomorphic(G1, G2) + assert nx.faster_could_be_isomorphic(G1, G2) == result + + +@pytest.mark.parametrize("atlas_ids", [(699, 706), (864, 870)]) +def test_could_be_isomorphic_combined_properties(atlas_ids): + """There are two pairs of graphs from the graph atlas that have the same + combined degree-triangle distribution, but a different maximal clique + distribution. See gh-7852.""" + G, H = (nx.graph_atlas(idx) for idx in atlas_ids) + + assert not nx.is_isomorphic(G, H) + + # Degree only + assert nx.faster_could_be_isomorphic(G, H) + assert nx.could_be_isomorphic(G, H, properties="d") + # Degrees & triangles + assert nx.fast_could_be_isomorphic(G, H) + assert nx.could_be_isomorphic(G, H, properties="dt") + # Full properties table (degrees, triangles, cliques) + assert not nx.could_be_isomorphic(G, H) + assert not nx.could_be_isomorphic(G, H, properties="dtc") + # For these two cases, the clique distribution alone is enough to verify + # the graphs can't be isomorphic + assert not nx.could_be_isomorphic(G, H, properties="c") + + +def test_could_be_isomorphic_individual_vs_combined_dt(): + """A test case where G and H have identical degree and triangle distributions, + but are different when compared together""" + G = nx.Graph([(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (3, 4), (4, 5), (4, 6)]) + H = G.copy() + # Modify graphs to produce different clique distributions + G.add_edge(0, 7) + H.add_edge(4, 7) + assert nx.could_be_isomorphic(G, H, properties="d") + assert nx.could_be_isomorphic(G, H, properties="t") + assert not nx.could_be_isomorphic(G, H, properties="dt") + assert not nx.could_be_isomorphic(G, H, properties="c") + + +class TestIsomorph: + @classmethod + def setup_class(cls): + cls.G1 = nx.Graph([[1, 2], [1, 3], [1, 5], [2, 3]]) + cls.G2 = nx.Graph([[10, 20], [20, 30], [10, 30], [10, 50]]) + cls.G3 = nx.Graph([[1, 2], [1, 3], [1, 5], [2, 5]]) + cls.G4 = nx.Graph([[1, 2], [1, 3], [1, 5], [2, 4]]) + cls.G5 = nx.Graph([[1, 2], [1, 3]]) + cls.G6 = nx.Graph([[10, 20], [20, 30], [10, 30], [10, 50], [20, 50]]) + + def test_could_be_isomorphic(self): + assert iso.could_be_isomorphic(self.G1, self.G2) + assert iso.could_be_isomorphic(self.G1, self.G3) + assert not iso.could_be_isomorphic(self.G1, self.G4) + assert iso.could_be_isomorphic(self.G3, self.G2) + assert not iso.could_be_isomorphic(self.G1, self.G6) + + @pytest.mark.parametrize("fn", (iso.fast_could_be_isomorphic, fast_cbi)) + def test_fast_could_be_isomorphic(self, fn): + assert fn(self.G3, self.G2) + assert not fn(self.G3, self.G5) + assert not fn(self.G1, self.G6) + + @pytest.mark.parametrize("fn", (iso.faster_could_be_isomorphic, faster_cbi)) + def test_faster_could_be_isomorphic(self, fn): + assert fn(self.G3, self.G2) + assert not fn(self.G3, self.G5) + assert not fn(self.G1, self.G6) + + def test_is_isomorphic(self): + assert iso.is_isomorphic(self.G1, self.G2) + assert not iso.is_isomorphic(self.G1, self.G4) + assert iso.is_isomorphic(self.G1.to_directed(), self.G2.to_directed()) + assert not iso.is_isomorphic(self.G1.to_directed(), self.G4.to_directed()) + with pytest.raises( + nx.NetworkXError, match="Graphs G1 and G2 are not of the same type." + ): + iso.is_isomorphic(self.G1.to_directed(), self.G1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py new file mode 100644 index 0000000000000000000000000000000000000000..657876e985733e4b807c53edc0594bbf5e1a4e7a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py @@ -0,0 +1,490 @@ +""" +Tests for VF2 isomorphism algorithm. +""" + +import importlib.resources +import random +import struct + +import pytest + +import networkx as nx +from networkx.algorithms import isomorphism as iso + + +class TestWikipediaExample: + # Source: https://en.wikipedia.org/wiki/Graph_isomorphism + + # Nodes 'a', 'b', 'c' and 'd' form a column. + # Nodes 'g', 'h', 'i' and 'j' form a column. + # isomorphism: {'a': 1, 'g': 2, 'b': 3, 'c': 6, 'h': 4, 'i': 5, 'j': 7, 'd': 8} + g1edges = [ + ["a", "g"], + ["a", "h"], + ["a", "i"], + ["b", "g"], + ["b", "h"], + ["b", "j"], + ["c", "g"], + ["c", "i"], + ["c", "j"], + ["d", "h"], + ["d", "i"], + ["d", "j"], + ] + + # Nodes 1,2,3,4 form the clockwise corners of a large square. + # Nodes 5,6,7,8 form the clockwise corners of a small square + g2edges = [ + [1, 2], + [3, 2], + [3, 4], + [1, 4], + [6, 5], + [6, 7], + [8, 7], + [8, 5], + [1, 5], + [6, 2], + [3, 7], + [8, 4], + ] + + @pytest.mark.parametrize( + ["graph_class", "numb_maps"], [(nx.Graph, 48), (nx.DiGraph, 24)] + ) + def test_morphic_and_number_of_mappings(self, graph_class, numb_maps): + g1 = graph_class(self.g1edges) + g2 = graph_class(self.g2edges) + Matcher = iso.DiGraphMatcher if g1.is_directed() else iso.GraphMatcher + gm = Matcher(g1, g2) + assert gm.is_isomorphic() + assert gm.subgraph_is_monomorphic() + assert gm.subgraph_is_isomorphic() + + mapping = list(gm.mapping.items()) + # this mapping is only one of the 48 possibilities + all_mappings = list(gm.isomorphisms_iter()) + assert len(all_mappings) == numb_maps + assert dict(mapping) in all_mappings + + @pytest.mark.parametrize("graph_class", [nx.Graph, nx.DiGraph]) + def test_subgraph(self, graph_class): + g1 = graph_class(self.g1edges) + g2 = graph_class(self.g2edges) + g3 = g2.subgraph([1, 2, 3, 4]) + Matcher = iso.DiGraphMatcher if g1.is_directed() else iso.GraphMatcher + gm = Matcher(g1, g3) + assert not gm.is_isomorphic() + assert gm.subgraph_is_isomorphic() + assert gm.subgraph_is_monomorphic() + + @pytest.mark.parametrize("graph_class", [nx.Graph, nx.DiGraph]) + def test_subgraph_mono(self, graph_class): + g1 = graph_class(self.g1edges) + g2 = graph_class(["ag", "cg", "ci", "di", "bg"]) + Matcher = iso.DiGraphMatcher if g1.is_directed() else iso.GraphMatcher + gm = Matcher(g1, g2) + assert not gm.is_isomorphic() + assert not gm.subgraph_is_isomorphic() + assert gm.subgraph_is_monomorphic() + + # note: this shows need to recreate the matcher for each graph change + # Also checking not monomorphic + g2.add_edge("c", "a") + gm = Matcher(g1, g2) + assert not gm.subgraph_is_monomorphic() + + +class TestVF2GraphDB: + # https://web.archive.org/web/20090303210205/http://amalfi.dis.unina.it/graph/db/ + + @staticmethod + def create_graph(filename): + """Creates a Graph instance from the filename.""" + + # The file is assumed to be in the format from the VF2 graph database. + # Each file is composed of 16-bit numbers (unsigned short int). + # So we will want to read 2 bytes at a time. + + # We can read the number as follows: + # number = struct.unpack('>> X = set(range(10)) + >>> def mod3(x, y): + ... return (x - y) % 3 == 0 + >>> equivalence_classes(X, mod3) # doctest: +SKIP + {frozenset({1, 4, 7}), frozenset({8, 2, 5}), frozenset({0, 9, 3, 6})} + """ + # For simplicity of implementation, we initialize the return value as a + # list of lists, then convert it to a set of sets at the end of the + # function. + blocks = [] + # Determine the equivalence class for each element of the iterable. + for y in iterable: + # Each element y must be in *exactly one* equivalence class. + # + # Each block is guaranteed to be non-empty + for block in blocks: + x = arbitrary_element(block) + if relation(x, y): + block.append(y) + break + else: + # If the element y is not part of any known equivalence class, it + # must be in its own, so we create a new singleton equivalence + # class for it. + blocks.append([y]) + return {frozenset(block) for block in blocks} + + +@nx._dispatchable(edge_attrs="weight", returns_graph=True) +def quotient_graph( + G, + partition, + edge_relation=None, + node_data=None, + edge_data=None, + weight="weight", + relabel=False, + create_using=None, +): + """Returns the quotient graph of `G` under the specified equivalence + relation on nodes. + + Parameters + ---------- + G : NetworkX graph + The graph for which to return the quotient graph with the + specified node relation. + + partition : function, or dict or list of lists, tuples or sets + If a function, this function must represent an equivalence + relation on the nodes of `G`. It must take two arguments *u* + and *v* and return True exactly when *u* and *v* are in the + same equivalence class. The equivalence classes form the nodes + in the returned graph. + + If a dict of lists/tuples/sets, the keys can be any meaningful + block labels, but the values must be the block lists/tuples/sets + (one list/tuple/set per block), and the blocks must form a valid + partition of the nodes of the graph. That is, each node must be + in exactly one block of the partition. + + If a list of sets, the list must form a valid partition of + the nodes of the graph. That is, each node must be in exactly + one block of the partition. + + edge_relation : Boolean function with two arguments + This function must represent an edge relation on the *blocks* of + the `partition` of `G`. It must take two arguments, *B* and *C*, + each one a set of nodes, and return True exactly when there should be + an edge joining block *B* to block *C* in the returned graph. + + If `edge_relation` is not specified, it is assumed to be the + following relation. Block *B* is related to block *C* if and + only if some node in *B* is adjacent to some node in *C*, + according to the edge set of `G`. + + node_data : function + This function takes one argument, *B*, a set of nodes in `G`, + and must return a dictionary representing the node data + attributes to set on the node representing *B* in the quotient graph. + If None, the following node attributes will be set: + + * 'graph', the subgraph of the graph `G` that this block + represents, + * 'nnodes', the number of nodes in this block, + * 'nedges', the number of edges within this block, + * 'density', the density of the subgraph of `G` that this + block represents. + + edge_data : function + This function takes two arguments, *B* and *C*, each one a set + of nodes, and must return a dictionary representing the edge + data attributes to set on the edge joining *B* and *C*, should + there be an edge joining *B* and *C* in the quotient graph (if + no such edge occurs in the quotient graph as determined by + `edge_relation`, then the output of this function is ignored). + + If the quotient graph would be a multigraph, this function is + not applied, since the edge data from each edge in the graph + `G` appears in the edges of the quotient graph. + + weight : string or None, optional (default="weight") + The name of an edge attribute that holds the numerical value + used as a weight. If None then each edge has weight 1. + + relabel : bool + If True, relabel the nodes of the quotient graph to be + nonnegative integers. Otherwise, the nodes are identified with + :class:`frozenset` instances representing the blocks given in + `partition`. + + create_using : NetworkX graph constructor, optional (default=nx.Graph) + Graph type to create. If graph instance, then cleared before populated. + + Returns + ------- + NetworkX graph + The quotient graph of `G` under the equivalence relation + specified by `partition`. If the partition were given as a + list of :class:`set` instances and `relabel` is False, + each node will be a :class:`frozenset` corresponding to the same + :class:`set`. + + Raises + ------ + NetworkXException + If the given partition is not a valid partition of the nodes of + `G`. + + Examples + -------- + The quotient graph of the complete bipartite graph under the "same + neighbors" equivalence relation is `K_2`. Under this relation, two nodes + are equivalent if they are not adjacent but have the same neighbor set. + + >>> G = nx.complete_bipartite_graph(2, 3) + >>> same_neighbors = lambda u, v: (u not in G[v] and v not in G[u] and G[u] == G[v]) + >>> Q = nx.quotient_graph(G, same_neighbors) + >>> K2 = nx.complete_graph(2) + >>> nx.is_isomorphic(Q, K2) + True + + The quotient graph of a directed graph under the "same strongly connected + component" equivalence relation is the condensation of the graph (see + :func:`condensation`). This example comes from the Wikipedia article + *`Strongly connected component`_*. + + >>> G = nx.DiGraph() + >>> edges = [ + ... "ab", + ... "be", + ... "bf", + ... "bc", + ... "cg", + ... "cd", + ... "dc", + ... "dh", + ... "ea", + ... "ef", + ... "fg", + ... "gf", + ... "hd", + ... "hf", + ... ] + >>> G.add_edges_from(tuple(x) for x in edges) + >>> components = list(nx.strongly_connected_components(G)) + >>> sorted(sorted(component) for component in components) + [['a', 'b', 'e'], ['c', 'd', 'h'], ['f', 'g']] + >>> + >>> C = nx.condensation(G, components) + >>> component_of = C.graph["mapping"] + >>> same_component = lambda u, v: component_of[u] == component_of[v] + >>> Q = nx.quotient_graph(G, same_component) + >>> nx.is_isomorphic(C, Q) + True + + Node identification can be represented as the quotient of a graph under the + equivalence relation that places the two nodes in one block and each other + node in its own singleton block. + + >>> K24 = nx.complete_bipartite_graph(2, 4) + >>> K34 = nx.complete_bipartite_graph(3, 4) + >>> C = nx.contracted_nodes(K34, 1, 2) + >>> nodes = {1, 2} + >>> is_contracted = lambda u, v: u in nodes and v in nodes + >>> Q = nx.quotient_graph(K34, is_contracted) + >>> nx.is_isomorphic(Q, C) + True + >>> nx.is_isomorphic(Q, K24) + True + + The blockmodeling technique described in [1]_ can be implemented as a + quotient graph. + + >>> G = nx.path_graph(6) + >>> partition = [{0, 1}, {2, 3}, {4, 5}] + >>> M = nx.quotient_graph(G, partition, relabel=True) + >>> list(M.edges()) + [(0, 1), (1, 2)] + + Here is the sample example but using partition as a dict of block sets. + + >>> G = nx.path_graph(6) + >>> partition = {0: {0, 1}, 2: {2, 3}, 4: {4, 5}} + >>> M = nx.quotient_graph(G, partition, relabel=True) + >>> list(M.edges()) + [(0, 1), (1, 2)] + + Partitions can be represented in various ways: + + 0. a list/tuple/set of block lists/tuples/sets + 1. a dict with block labels as keys and blocks lists/tuples/sets as values + 2. a dict with block lists/tuples/sets as keys and block labels as values + 3. a function from nodes in the original iterable to block labels + 4. an equivalence relation function on the target iterable + + As `quotient_graph` is designed to accept partitions represented as (0), (1) or + (4) only, the `equivalence_classes` function can be used to get the partitions + in the right form, in order to call `quotient_graph`. + + .. _Strongly connected component: https://en.wikipedia.org/wiki/Strongly_connected_component + + References + ---------- + .. [1] Patrick Doreian, Vladimir Batagelj, and Anuska Ferligoj. + *Generalized Blockmodeling*. + Cambridge University Press, 2004. + + """ + # If the user provided an equivalence relation as a function to compute + # the blocks of the partition on the nodes of G induced by the + # equivalence relation. + if callable(partition): + # equivalence_classes always return partition of whole G. + partition = equivalence_classes(G, partition) + if not nx.community.is_partition(G, partition): + raise nx.NetworkXException( + "Input `partition` is not an equivalence relation for nodes of G" + ) + return _quotient_graph( + G, + partition, + edge_relation, + node_data, + edge_data, + weight, + relabel, + create_using, + ) + + # If the partition is a dict, it is assumed to be one where the keys are + # user-defined block labels, and values are block lists, tuples or sets. + if isinstance(partition, dict): + partition = list(partition.values()) + + # If the user provided partition as a collection of sets. Then we + # need to check if partition covers all of G nodes. If the answer + # is 'No' then we need to prepare suitable subgraph view. + partition_nodes = set().union(*partition) + if len(partition_nodes) != len(G): + G = G.subgraph(partition_nodes) + # Each node in the graph/subgraph must be in exactly one block. + if not nx.community.is_partition(G, partition): + raise NetworkXException("each node must be in exactly one part of `partition`") + return _quotient_graph( + G, + partition, + edge_relation, + node_data, + edge_data, + weight, + relabel, + create_using, + ) + + +def _quotient_graph( + G, partition, edge_relation, node_data, edge_data, weight, relabel, create_using +): + """Construct the quotient graph assuming input has been checked""" + if create_using is None: + H = G.__class__() + else: + H = nx.empty_graph(0, create_using) + # By default set some basic information about the subgraph that each block + # represents on the nodes in the quotient graph. + if node_data is None: + + def node_data(b): + S = G.subgraph(b) + return { + "graph": S, + "nnodes": len(S), + "nedges": S.number_of_edges(), + "density": density(S), + } + + # Each block of the partition becomes a node in the quotient graph. + partition = [frozenset(b) for b in partition] + H.add_nodes_from((b, node_data(b)) for b in partition) + # By default, the edge relation is the relation defined as follows. B is + # adjacent to C if a node in B is adjacent to a node in C, according to the + # edge set of G. + # + # This is not a particularly efficient implementation of this relation: + # there are O(n^2) pairs to check and each check may require O(log n) time + # (to check set membership). This can certainly be parallelized. + if edge_relation is None: + + def edge_relation(b, c): + return any(v in G[u] for u, v in product(b, c)) + + # By default, sum the weights of the edges joining pairs of nodes across + # blocks to get the weight of the edge joining those two blocks. + if edge_data is None: + + def edge_data(b, c): + edgedata = ( + d + for u, v, d in G.edges(b | c, data=True) + if (u in b and v in c) or (u in c and v in b) + ) + return {"weight": sum(d.get(weight, 1) for d in edgedata)} + + block_pairs = permutations(H, 2) if H.is_directed() else combinations(H, 2) + # In a multigraph, add one edge in the quotient graph for each edge + # in the original graph. + if H.is_multigraph(): + edges = chaini( + ( + (b, c, G.get_edge_data(u, v, default={})) + for u, v in product(b, c) + if v in G[u] + ) + for b, c in block_pairs + if edge_relation(b, c) + ) + # In a simple graph, apply the edge data function to each pair of + # blocks to determine the edge data attributes to apply to each edge + # in the quotient graph. + else: + edges = ( + (b, c, edge_data(b, c)) for (b, c) in block_pairs if edge_relation(b, c) + ) + H.add_edges_from(edges) + # If requested by the user, relabel the nodes to be integers, + # numbered in increasing order from zero in the same order as the + # iteration order of `partition`. + if relabel: + # Can't use nx.convert_node_labels_to_integers() here since we + # want the order of iteration to be the same for backward + # compatibility with the nx.blockmodel() function. + labels = {b: i for i, b in enumerate(partition)} + H = nx.relabel_nodes(H, labels) + return H + + +@nx._dispatchable( + preserve_all_attrs=True, mutates_input={"not copy": 4}, returns_graph=True +) +def contracted_nodes( + G, u, v, self_loops=True, copy=True, *, store_contraction_as="contraction" +): + """Returns the graph that results from contracting `u` and `v`. + + Node contraction identifies the two nodes as a single node incident to any + edge that was incident to the original two nodes. + Information about the contracted nodes and any modified edges are stored on + the output graph in a ``"contraction"`` attribute - see Examples for details. + + Parameters + ---------- + G : NetworkX graph + The graph whose nodes will be contracted. + + u, v : nodes + Must be nodes in `G`. + + self_loops : Boolean + If this is True, any edges joining `u` and `v` in `G` become + self-loops on the new node in the returned graph. + + copy : Boolean + If this is True (the default), make a copy of + `G` and return that instead of directly changing `G`. + + store_contraction_as : str or None, default="contraction" + Name of the node/edge attribute where information about the contraction + should be stored. By default information about the contracted node and + any contracted edges is stored in a ``"contraction"`` attribute on the + resulting node and edge. If `None`, information about the contracted + nodes/edges and their data are not stored. + + Returns + ------- + Networkx graph + If `copy` is True, + A new graph object of the same type as `G` (leaving `G` unmodified) + with `u` and `v` identified in a single node. The right node `v` + will be merged into the node `u`, so only `u` will appear in the + returned graph. + If `copy` is False, + Modifies `G` with `u` and `v` identified in a single node. + The right node `v` will be merged into the node `u`, so + only `u` will appear in the returned graph. + + Notes + ----- + For multigraphs, the edge keys for the realigned edges may + not be the same as the edge keys for the old edges. This is + natural because edge keys are unique only within each pair of nodes. + + This function is also available as `identified_nodes`. + + Examples + -------- + Contracting two nonadjacent nodes of the cycle graph on four nodes `C_4` + yields the path graph (ignoring parallel edges): + + >>> G = nx.cycle_graph(4) + >>> M = nx.contracted_nodes(G, 1, 3) + >>> P3 = nx.path_graph(3) + >>> nx.is_isomorphic(M, P3) + True + + Information about the contracted nodes is stored on the resulting graph in + a ``"contraction"`` attribute. For instance, the contracted node is stored + as an attribute on ``u``: + + >>> H = nx.contracted_nodes(P3, 0, 2) + >>> H.nodes(data=True) + NodeDataView({0: {'contraction': {2: {}}}, 1: {}}) + + Any node attributes on the contracted node are also preserved: + + >>> nx.set_node_attributes(P3, dict(enumerate("rgb")), name="color") + >>> P3.nodes(data=True) + NodeDataView({0: {'color': 'r'}, 1: {'color': 'g'}, 2: {'color': 'b'}}) + >>> H = nx.contracted_nodes(P3, 0, 2) + >>> H.nodes[0] + {'color': 'r', 'contraction': {2: {'color': 'b'}}} + + Edges are handled similarly: when ``u`` and ``v`` are adjacent to a third node + ``w``, the edge ``(v, w)`` will be contracted into the edge ``(u, w)`` with + its attributes stored into a ``"contraction"`` attribute on edge ``(u, w)``: + + >>> nx.set_edge_attributes(P3, {(0, 1): 10, (1, 2): 100}, name="weight") + >>> P3.edges(data=True) + EdgeDataView([(0, 1, {'weight': 10}), (1, 2, {'weight': 100})]) + >>> H = nx.contracted_nodes(P3, 0, 2) + >>> H.edges(data=True) + EdgeDataView([(0, 1, {'weight': 10, 'contraction': {(2, 1): {'weight': 100}}})]) + + Attributes from contracted nodes/edges can be combined with those of the + nodes/edges onto which they were contracted: + + >>> # Concatenate colors of contracted nodes + >>> for u, cdict in H.nodes(data="contraction"): + ... if cdict is not None: + ... H.nodes[u]["color"] += "".join(n["color"] for n in cdict.values()) + ... del H.nodes[u]["contraction"] # Remove contraction attr (optional) + >>> H.nodes(data=True) + NodeDataView({0: {'color': 'rb'}, 1: {'color': 'g'}}) + >>> # Sum contracted edge weights + >>> for u, v, cdict in H.edges(data="contraction"): + ... if cdict is not None: + ... H[u][v]["weight"] += sum(n["weight"] for n in cdict.values()) + ... del H.edges[(u, v)]["contraction"] # Remove contraction attr (optional) + >>> H.edges(data=True) + EdgeDataView([(0, 1, {'weight': 110})]) + + If `G` is a multigraph, then a new edge is added instead. Any edge attributes + are still preserved: + + >>> MG = nx.MultiGraph(P3) + >>> MH = nx.contracted_nodes(MG, 0, 2) + >>> MH.edges(keys=True, data=True) + MultiEdgeDataView([(0, 1, 0, {'weight': 10}), (0, 1, 1, {'weight': 100})]) + + If ``selfloops=True`` (the default), any edges adjoining `u` and `v` become + self-loops on ``u`` in the resulting graph: + + >>> G = nx.Graph([(1, 2)]) + >>> H = nx.contracted_nodes(G, 1, 2) + >>> H.nodes, H.edges + (NodeView((1,)), EdgeView([(1, 1)])) + >>> H = nx.contracted_nodes(G, 1, 2, self_loops=False) + >>> H.nodes, H.edges + (NodeView((1,)), EdgeView([])) + + Note however that any self loops in the original graph `G` are preserved: + + >>> G = nx.Graph([(1, 2), (2, 2)]) + >>> H = nx.contracted_nodes(G, 1, 2, self_loops=False) + >>> H.nodes, H.edges + (NodeView((1,)), EdgeView([(1, 1)])) + + The same reasoning applies to MultiGraphs: + + >>> MG = nx.MultiGraph([(1, 2), (2, 2)]) + >>> # Edge (1, 1, 0) in MH corresponds to edge (2, 2) in MG + >>> MH = nx.contracted_nodes(MG, 1, 2, self_loops=False) + >>> MH.edges(keys=True) + MultiEdgeView([(1, 1, 0)]) + >>> # MH has two (1, 1) edges - one from edge (2, 2) in MG, and one + >>> # resulting from the contraction of 2->1 + >>> MH = nx.contracted_nodes(MG, 1, 2, self_loops=True) + >>> MH.edges(keys=True) + MultiEdgeView([(1, 1, 0), (1, 1, 1)]) + + + In a ``MultiDiGraph`` with a self loop, the in and out edges will + be treated separately as edges, so while contracting a node which + has a self loop the contraction will add multiple edges: + + >>> G = nx.MultiDiGraph([(1, 2), (2, 2)]) + >>> H = nx.contracted_nodes(G, 1, 2) + >>> list(H.edges()) # edge 1->2, 2->2, 2<-2 from the original Graph G + [(1, 1), (1, 1), (1, 1)] + >>> H = nx.contracted_nodes(G, 1, 2, self_loops=False) + >>> list(H.edges()) # edge 2->2, 2<-2 from the original Graph G + [(1, 1), (1, 1)] + + See Also + -------- + contracted_edge + quotient_graph + + """ + # Copying has significant overhead and can be disabled if needed + H = G.copy() if copy else G + + # edge code uses G.edges(v) instead of G.adj[v] to handle multiedges + if H.is_directed(): + edges_to_remap = chain(G.in_edges(v, data=True), G.out_edges(v, data=True)) + else: + edges_to_remap = G.edges(v, data=True) + + # If the H=G, the generators change as H changes + # This makes the edges_to_remap independent of H + if not copy: + edges_to_remap = list(edges_to_remap) + + v_data = H.nodes[v] + H.remove_node(v) + + # A bit of input munging to extract whether contraction info should be + # stored, and if so bind to a shorter name + if _store_contraction := (store_contraction_as is not None): + contraction = store_contraction_as + + for prev_w, prev_x, d in edges_to_remap: + w = prev_w if prev_w != v else u + x = prev_x if prev_x != v else u + + if ({prev_w, prev_x} == {u, v}) and not self_loops: + continue + + if not H.has_edge(w, x) or G.is_multigraph(): + H.add_edge(w, x, **d) + continue + + # Store information about the contracted edge iff `store_contraction` is not None + if _store_contraction: + if contraction in H.edges[(w, x)]: + H.edges[(w, x)][contraction][(prev_w, prev_x)] = d + else: + H.edges[(w, x)][contraction] = {(prev_w, prev_x): d} + + # Store information about the contracted node iff `store_contraction` + if _store_contraction: + if contraction in H.nodes[u]: + H.nodes[u][contraction][v] = v_data + else: + H.nodes[u][contraction] = {v: v_data} + + return H + + +identified_nodes = contracted_nodes + + +@nx._dispatchable( + preserve_all_attrs=True, mutates_input={"not copy": 3}, returns_graph=True +) +def contracted_edge( + G, edge, self_loops=True, copy=True, *, store_contraction_as="contraction" +): + """Returns the graph that results from contracting the specified edge. + + Edge contraction identifies the two endpoints of the edge as a single node + incident to any edge that was incident to the original two nodes. A graph + that results from edge contraction is called a *minor* of the original + graph. + + Parameters + ---------- + G : NetworkX graph + The graph whose edge will be contracted. + + edge : tuple + Must be a pair of nodes in `G`. + + self_loops : Boolean + If this is True, any edges (including `edge`) joining the + endpoints of `edge` in `G` become self-loops on the new node in the + returned graph. + + copy : Boolean (default True) + If this is True, a the contraction will be performed on a copy of `G`, + otherwise the contraction will happen in place. + + store_contraction_as : str or None, default="contraction" + Name of the node/edge attribute where information about the contraction + should be stored. By default information about the contracted node and + any contracted edges is stored in a ``"contraction"`` attribute on the + resulting node and edge. If `None`, information about the contracted + nodes/edges and their data are not stored. + + Returns + ------- + Networkx graph + A new graph object of the same type as `G` (leaving `G` unmodified) + with endpoints of `edge` identified in a single node. The right node + of `edge` will be merged into the left one, so only the left one will + appear in the returned graph. + + Raises + ------ + ValueError + If `edge` is not an edge in `G`. + + Examples + -------- + Attempting to contract two nonadjacent nodes yields an error: + + >>> G = nx.cycle_graph(4) + >>> nx.contracted_edge(G, (1, 3)) + Traceback (most recent call last): + ... + ValueError: Edge (1, 3) does not exist in graph G; cannot contract it + + Contracting two adjacent nodes in the cycle graph on *n* nodes yields the + cycle graph on *n - 1* nodes: + + >>> C5 = nx.cycle_graph(5) + >>> C4 = nx.cycle_graph(4) + >>> M = nx.contracted_edge(C5, (0, 1), self_loops=False) + >>> nx.is_isomorphic(M, C4) + True + + See also + -------- + contracted_nodes + quotient_graph + + """ + u, v = edge[:2] + if not G.has_edge(u, v): + raise ValueError(f"Edge {edge} does not exist in graph G; cannot contract it") + return contracted_nodes( + G, + u, + v, + self_loops=self_loops, + copy=copy, + store_contraction_as=store_contraction_as, + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/__pycache__/test_contraction.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/__pycache__/test_contraction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7febcea19dc45dd623f27e7992e8aad7efa98811 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/__pycache__/test_contraction.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/test_contraction.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/test_contraction.py new file mode 100644 index 0000000000000000000000000000000000000000..2fdfe575b5da0b5aa5a0fdb073926d04149f4161 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/minors/tests/test_contraction.py @@ -0,0 +1,544 @@ +"""Unit tests for the :mod:`networkx.algorithms.minors.contraction` module.""" + +import pytest + +import networkx as nx +from networkx.utils import arbitrary_element, edges_equal, nodes_equal + + +def test_quotient_graph_complete_multipartite(): + """Tests that the quotient graph of the complete *n*-partite graph + under the "same neighbors" node relation is the complete graph on *n* + nodes. + + """ + G = nx.complete_multipartite_graph(2, 3, 4) + # Two nodes are equivalent if they are not adjacent but have the same + # neighbor set. + + def same_neighbors(u, v): + return u not in G[v] and v not in G[u] and G[u] == G[v] + + expected = nx.complete_graph(3) + actual = nx.quotient_graph(G, same_neighbors) + # It won't take too long to run a graph isomorphism algorithm on such + # small graphs. + assert nx.is_isomorphic(expected, actual) + + +def test_quotient_graph_complete_bipartite(): + """Tests that the quotient graph of the complete bipartite graph under + the "same neighbors" node relation is `K_2`. + + """ + G = nx.complete_bipartite_graph(2, 3) + # Two nodes are equivalent if they are not adjacent but have the same + # neighbor set. + + def same_neighbors(u, v): + return u not in G[v] and v not in G[u] and G[u] == G[v] + + expected = nx.complete_graph(2) + actual = nx.quotient_graph(G, same_neighbors) + # It won't take too long to run a graph isomorphism algorithm on such + # small graphs. + assert nx.is_isomorphic(expected, actual) + + +def test_quotient_graph_edge_relation(): + """Tests for specifying an alternate edge relation for the quotient + graph. + + """ + G = nx.path_graph(5) + + def identity(u, v): + return u == v + + def same_parity(b, c): + return arbitrary_element(b) % 2 == arbitrary_element(c) % 2 + + actual = nx.quotient_graph(G, identity, same_parity) + expected = nx.Graph() + expected.add_edges_from([(0, 2), (0, 4), (2, 4)]) + expected.add_edge(1, 3) + assert nx.is_isomorphic(actual, expected) + + +def test_condensation_as_quotient(): + """This tests that the condensation of a graph can be viewed as the + quotient graph under the "in the same connected component" equivalence + relation. + + """ + # This example graph comes from the file `test_strongly_connected.py`. + G = nx.DiGraph() + G.add_edges_from( + [ + (1, 2), + (2, 3), + (2, 11), + (2, 12), + (3, 4), + (4, 3), + (4, 5), + (5, 6), + (6, 5), + (6, 7), + (7, 8), + (7, 9), + (7, 10), + (8, 9), + (9, 7), + (10, 6), + (11, 2), + (11, 4), + (11, 6), + (12, 6), + (12, 11), + ] + ) + scc = list(nx.strongly_connected_components(G)) + C = nx.condensation(G, scc) + component_of = C.graph["mapping"] + # Two nodes are equivalent if they are in the same connected component. + + def same_component(u, v): + return component_of[u] == component_of[v] + + Q = nx.quotient_graph(G, same_component) + assert nx.is_isomorphic(C, Q) + + +def test_path(): + G = nx.path_graph(6) + partition = [{0, 1}, {2, 3}, {4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_path__partition_provided_as_dict_of_lists(): + G = nx.path_graph(6) + partition = {0: [0, 1], 2: [2, 3], 4: [4, 5]} + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_path__partition_provided_as_dict_of_tuples(): + G = nx.path_graph(6) + partition = {0: (0, 1), 2: (2, 3), 4: (4, 5)} + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_path__partition_provided_as_dict_of_sets(): + G = nx.path_graph(6) + partition = {0: {0, 1}, 2: {2, 3}, 4: {4, 5}} + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_multigraph_path(): + G = nx.MultiGraph(nx.path_graph(6)) + partition = [{0, 1}, {2, 3}, {4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_directed_path(): + G = nx.DiGraph() + nx.add_path(G, range(6)) + partition = [{0, 1}, {2, 3}, {4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)], directed=True) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 0.5 + + +def test_directed_multigraph_path(): + G = nx.MultiDiGraph() + nx.add_path(G, range(6)) + partition = [{0, 1}, {2, 3}, {4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)], directed=True) + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 0.5 + + +def test_overlapping_blocks(): + with pytest.raises(nx.NetworkXException): + G = nx.path_graph(6) + partition = [{0, 1, 2}, {2, 3}, {4, 5}] + nx.quotient_graph(G, partition) + + +def test_weighted_path(): + G = nx.path_graph(6) + for i in range(5): + G[i][i + 1]["w"] = i + 1 + partition = [{0, 1}, {2, 3}, {4, 5}] + M = nx.quotient_graph(G, partition, weight="w", relabel=True) + assert nodes_equal(M, [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + assert M[0][1]["weight"] == 2 + assert M[1][2]["weight"] == 4 + for n in M: + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1 + + +def test_barbell(): + G = nx.barbell_graph(3, 0) + partition = [{0, 1, 2}, {3, 4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1]) + assert edges_equal(M.edges(), [(0, 1)]) + for n in M: + assert M.nodes[n]["nedges"] == 3 + assert M.nodes[n]["nnodes"] == 3 + assert M.nodes[n]["density"] == 1 + + +def test_barbell_plus(): + G = nx.barbell_graph(3, 0) + # Add an extra edge joining the bells. + G.add_edge(0, 5) + partition = [{0, 1, 2}, {3, 4, 5}] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M, [0, 1]) + assert edges_equal(M.edges(), [(0, 1)]) + assert M[0][1]["weight"] == 2 + for n in M: + assert M.nodes[n]["nedges"] == 3 + assert M.nodes[n]["nnodes"] == 3 + assert M.nodes[n]["density"] == 1 + + +def test_blockmodel(): + G = nx.path_graph(6) + partition = [[0, 1], [2, 3], [4, 5]] + M = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(M.nodes(), [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M.nodes(): + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1.0 + + +def test_multigraph_blockmodel(): + G = nx.MultiGraph(nx.path_graph(6)) + partition = [[0, 1], [2, 3], [4, 5]] + M = nx.quotient_graph(G, partition, create_using=nx.MultiGraph(), relabel=True) + assert nodes_equal(M.nodes(), [0, 1, 2]) + assert edges_equal(M.edges(), [(0, 1), (1, 2)]) + for n in M.nodes(): + assert M.nodes[n]["nedges"] == 1 + assert M.nodes[n]["nnodes"] == 2 + assert M.nodes[n]["density"] == 1.0 + + +def test_quotient_graph_incomplete_partition(): + G = nx.path_graph(6) + partition = [] + H = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(H.nodes(), []) + assert edges_equal(H.edges(), []) + + partition = [[0, 1], [2, 3], [5]] + H = nx.quotient_graph(G, partition, relabel=True) + assert nodes_equal(H.nodes(), [0, 1, 2]) + assert edges_equal(H.edges(), [(0, 1)]) + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", (True, False)) +@pytest.mark.parametrize("selfloops", (True, False)) +def test_undirected_node_contraction(store_contraction_as, copy, selfloops): + """Tests for node contraction in an undirected graph.""" + G = nx.cycle_graph(4) + actual = nx.contracted_nodes( + G, + 0, + 1, + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + + expected = nx.cycle_graph(3) + if selfloops: + expected.add_edge(0, 0) + + assert nx.is_isomorphic(actual, expected) + + if not copy: + assert actual is G + + # Test contracted node attributes + if store_contraction_as is not None: + assert actual.nodes[0][store_contraction_as] == {1: {}} + else: + assert actual.nodes[0] == {} + # There should be no contracted edges for this case + assert all(d == {} for _, _, d in actual.edges(data=True)) + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", (True, False)) +@pytest.mark.parametrize("selfloops", (True, False)) +def test_directed_node_contraction(store_contraction_as, copy, selfloops): + """Tests for node contraction in a directed graph.""" + G = nx.DiGraph(nx.cycle_graph(4)) + actual = nx.contracted_nodes( + G, + 0, + 1, + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + + expected = nx.DiGraph(nx.cycle_graph(3)) + if selfloops: + expected.add_edge(0, 0) + + assert nx.is_isomorphic(actual, expected) + + if not copy: + assert actual is G + # Test contracted node attributes + if store_contraction_as is not None: + assert actual.nodes[0][store_contraction_as] == {1: {}} + else: + assert actual.nodes[0] == {} + # Test contracted edge attributes (only relevant if self loops is enabled) + if selfloops and store_contraction_as: + assert actual.edges[(0, 0)][store_contraction_as] == {(1, 0): {}} + else: + assert all(d == {} for _, _, d in actual.edges(data=True)) + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", (True, False)) +@pytest.mark.parametrize("selfloops", (True, False)) +def test_contracted_nodes_multigraph(store_contraction_as, copy, selfloops): + """Tests that using a MultiGraph creates multiple edges. `store_contraction_as` + has no effect for multigraphs.""" + G = nx.path_graph(3, create_using=nx.MultiGraph) + G.add_edges_from([(0, 1), (0, 0), (0, 2)]) + actual = nx.contracted_nodes( + G, + 0, + 2, + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + # Two (0, 1) edges from G, another from the contraction of edge (1, 2) + expected = nx.MultiGraph([(0, 1), (0, 1), (0, 1), (0, 0)]) + # One (0, 0) edge from G, another from the contraction of edge (0, 2), but + # only if `selfloops` is True + if selfloops: + expected.add_edge(0, 0) + + assert edges_equal(actual.edges, expected.edges) + if not copy: + assert actual is G + + +def test_multigraph_keys(): + """Tests that multiedge keys are reset in new graph.""" + G = nx.path_graph(3, create_using=nx.MultiGraph()) + G.add_edge(0, 1, 5) + G.add_edge(0, 0, 0) + G.add_edge(0, 2, 5) + actual = nx.contracted_nodes(G, 0, 2) + expected = nx.MultiGraph() + expected.add_edge(0, 1, 0) + expected.add_edge(0, 1, 5) + expected.add_edge(0, 1, 2) # keyed as 2 b/c 2 edges already in G + expected.add_edge(0, 0, 0) + expected.add_edge(0, 0, 1) # this comes from (0, 2, 5) + assert edges_equal(actual.edges, expected.edges) + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", (True, False)) +@pytest.mark.parametrize("selfloops", (True, False)) +def test_node_attributes(store_contraction_as, copy, selfloops): + """Tests that node contraction preserves node attributes.""" + G = nx.cycle_graph(4) + # Add some data to the two nodes being contracted. + G.nodes[0]["foo"] = "bar" + G.nodes[1]["baz"] = "xyzzy" + actual = nx.contracted_nodes( + G, + 0, + 1, + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + # We expect that contracting the nodes 0 and 1 in C_4 yields K_3, but + # with nodes labeled 0, 2, and 3. + expected = nx.complete_graph(3) + expected = nx.relabel_nodes(expected, {1: 2, 2: 3}) + expected.nodes[0]["foo"] = "bar" + # ... and a self-loop (0, 0), if self_loops=True + if selfloops: + expected.add_edge(0, 0) + + if store_contraction_as: + cdict = {1: {"baz": "xyzzy"}} + expected.nodes[0].update({"foo": "bar", store_contraction_as: cdict}) + + assert nx.is_isomorphic(actual, expected) + assert actual.nodes(data=True) == expected.nodes(data=True) + if not copy: + assert actual is G + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +def test_edge_attributes(store_contraction_as): + """Tests that node contraction preserves edge attributes.""" + # Shape: src1 --> dest <-- src2 + G = nx.DiGraph([("src1", "dest"), ("src2", "dest")]) + G["src1"]["dest"]["value"] = "src1-->dest" + G["src2"]["dest"]["value"] = "src2-->dest" + + # New Shape: src1 --> dest + H = nx.contracted_nodes( + G, "src1", "src2", store_contraction_as=store_contraction_as + ) + assert H.edges[("src1", "dest")]["value"] == "src1-->dest" # Should be unchanged + if store_contraction_as: + assert ( + H.edges[("src1", "dest")][store_contraction_as][("src2", "dest")]["value"] + == "src2-->dest" + ) + else: + assert store_contraction_as not in H.edges[("src1", "dest")] + + G = nx.MultiDiGraph(G) + # New Shape: src1 -(x2)-> dest + H = nx.contracted_nodes( + G, "src1", "src2", store_contraction_as=store_contraction_as + ) + # store_contraction should not affect multigraphs + assert len(H.edges(("src1", "dest"))) == 2 + assert H.edges[("src1", "dest", 0)]["value"] == "src1-->dest" + assert H.edges[("src1", "dest", 1)]["value"] == "src2-->dest" + + +def test_contract_loop_graph(): + """Tests for node contraction when nodes have loops.""" + G = nx.cycle_graph(4) + G.add_edge(0, 0) + actual = nx.contracted_nodes(G, 0, 1) + expected = nx.complete_graph([0, 2, 3]) + expected.add_edge(0, 0) + assert edges_equal(actual.edges, expected.edges) + actual = nx.contracted_nodes(G, 1, 0) + expected = nx.complete_graph([1, 2, 3]) + expected.add_edge(1, 1) + assert edges_equal(actual.edges, expected.edges) + + +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", (True, False)) +@pytest.mark.parametrize("selfloops", (True, False)) +def test_undirected_edge_contraction(store_contraction_as, copy, selfloops): + """Tests for node contraction in an undirected graph.""" + G = nx.cycle_graph(4) + actual = nx.contracted_edge( + G, + (0, 1), + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + + expected = nx.cycle_graph(3) + if selfloops: + expected.add_edge(0, 0) + + assert nx.is_isomorphic(actual, expected) + + if not copy: + assert actual is G + + # Test contracted node attributes + if store_contraction_as is not None: + assert actual.nodes[0][store_contraction_as] == {1: {}} + else: + assert actual.nodes[0] == {} + # There should be no contracted edges for this case + assert all(d == {} for _, _, d in actual.edges(data=True)) + + +@pytest.mark.parametrize("edge", [(0, 1), (0, 1, 0)]) +@pytest.mark.parametrize("store_contraction_as", ("contraction", "c", None)) +@pytest.mark.parametrize("copy", [True, False]) +@pytest.mark.parametrize("selfloops", [True, False]) +def test_multigraph_edge_contraction(edge, store_contraction_as, copy, selfloops): + """Tests for edge contraction in a multigraph""" + G = nx.cycle_graph(4, create_using=nx.MultiGraph) + actual = nx.contracted_edge( + G, + edge, + copy=copy, + self_loops=selfloops, + store_contraction_as=store_contraction_as, + ) + expected = nx.relabel_nodes( + nx.complete_graph(3, create_using=nx.MultiGraph), {0: 0, 1: 2, 2: 3} + ) + if selfloops: + expected.add_edge(0, 0) + + assert edges_equal(actual.edges, expected.edges) + if not copy: + assert actual is G + + +def test_nonexistent_edge(): + """Tests that attempting to contract a nonexistent edge raises an + exception. + + """ + G = nx.cycle_graph(4) + with pytest.raises(ValueError): + nx.contracted_edge(G, (0, 2)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0ebc6ab9998db144234c2601c24861b2c48fa339 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__init__.py @@ -0,0 +1,4 @@ +from networkx.algorithms.operators.all import * +from networkx.algorithms.operators.binary import * +from networkx.algorithms.operators.product import * +from networkx.algorithms.operators.unary import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb81cfb8dfa14db79f0641f6763bf4a9265d7ec4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/all.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/all.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..241de81d2c056cf160378757451dc98f5e173aae Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/all.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/binary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/binary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..775b1b5c8e0163b499a7745ddb9519f56d96aa9c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/binary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/product.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/product.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a1268f0b002025654dc0c0ce56886212a6c14ce Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/product.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/unary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/unary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9b96a64f27a176042aa7d4a26c0ac0501dc163c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/__pycache__/unary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/all.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/all.py new file mode 100644 index 0000000000000000000000000000000000000000..322a15ace64c99f0175eae4647ac39410d6c1b26 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/all.py @@ -0,0 +1,324 @@ +"""Operations on many graphs.""" + +from itertools import chain, repeat + +import networkx as nx + +__all__ = ["union_all", "compose_all", "disjoint_union_all", "intersection_all"] + + +@nx._dispatchable(graphs="[graphs]", preserve_all_attrs=True, returns_graph=True) +def union_all(graphs, rename=()): + """Returns the union of all graphs. + + The graphs must be disjoint, otherwise an exception is raised. + + Parameters + ---------- + graphs : iterable + Iterable of NetworkX graphs + + rename : iterable , optional + Node names of graphs can be changed by specifying the tuple + rename=('G-','H-') (for example). Node "u" in G is then renamed + "G-u" and "v" in H is renamed "H-v". Infinite generators (like itertools.count) + are also supported. + + Returns + ------- + U : a graph with the same type as the first graph in list + + Raises + ------ + ValueError + If `graphs` is an empty list. + + NetworkXError + In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. + + Notes + ----- + For operating on mixed type graphs, they should be converted to the same type. + >>> G = nx.Graph() + >>> H = nx.DiGraph() + >>> GH = union_all([nx.DiGraph(G), H]) + + To force a disjoint union with node relabeling, use + disjoint_union_all(G,H) or convert_node_labels_to integers(). + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + + Examples + -------- + >>> G1 = nx.Graph([(1, 2), (2, 3)]) + >>> G2 = nx.Graph([(4, 5), (5, 6)]) + >>> result_graph = nx.union_all([G1, G2]) + >>> result_graph.nodes() + NodeView((1, 2, 3, 4, 5, 6)) + >>> result_graph.edges() + EdgeView([(1, 2), (2, 3), (4, 5), (5, 6)]) + + See Also + -------- + union + disjoint_union_all + """ + R = None + seen_nodes = set() + + # rename graph to obtain disjoint node labels + def add_prefix(graph, prefix): + if prefix is None: + return graph + + def label(x): + return f"{prefix}{x}" + + return nx.relabel_nodes(graph, label) + + rename = chain(rename, repeat(None)) + graphs = (add_prefix(G, name) for G, name in zip(graphs, rename)) + + for i, G in enumerate(graphs): + G_nodes_set = set(G.nodes) + if i == 0: + # Union is the same type as first graph + R = G.__class__() + elif G.is_directed() != R.is_directed(): + raise nx.NetworkXError("All graphs must be directed or undirected.") + elif G.is_multigraph() != R.is_multigraph(): + raise nx.NetworkXError("All graphs must be graphs or multigraphs.") + elif not seen_nodes.isdisjoint(G_nodes_set): + raise nx.NetworkXError( + "The node sets of the graphs are not disjoint.\n" + "Use `rename` to specify prefixes for the graphs or use\n" + "disjoint_union(G1, G2, ..., GN)." + ) + + seen_nodes |= G_nodes_set + R.graph.update(G.graph) + R.add_nodes_from(G.nodes(data=True)) + R.add_edges_from( + G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) + ) + + if R is None: + raise ValueError("cannot apply union_all to an empty list") + + return R + + +@nx._dispatchable(graphs="[graphs]", preserve_all_attrs=True, returns_graph=True) +def disjoint_union_all(graphs): + """Returns the disjoint union of all graphs. + + This operation forces distinct integer node labels starting with 0 + for the first graph in the list and numbering consecutively. + + Parameters + ---------- + graphs : iterable + Iterable of NetworkX graphs + + Returns + ------- + U : A graph with the same type as the first graph in list + + Raises + ------ + ValueError + If `graphs` is an empty list. + + NetworkXError + In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. + + Examples + -------- + >>> G1 = nx.Graph([(1, 2), (2, 3)]) + >>> G2 = nx.Graph([(4, 5), (5, 6)]) + >>> U = nx.disjoint_union_all([G1, G2]) + >>> list(U.nodes()) + [0, 1, 2, 3, 4, 5] + >>> list(U.edges()) + [(0, 1), (1, 2), (3, 4), (4, 5)] + + Notes + ----- + For operating on mixed type graphs, they should be converted to the same type. + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + """ + + def yield_relabeled(graphs): + first_label = 0 + for G in graphs: + yield nx.convert_node_labels_to_integers(G, first_label=first_label) + first_label += len(G) + + R = union_all(yield_relabeled(graphs)) + + return R + + +@nx._dispatchable(graphs="[graphs]", preserve_all_attrs=True, returns_graph=True) +def compose_all(graphs): + """Returns the composition of all graphs. + + Composition is the simple union of the node sets and edge sets. + The node sets of the supplied graphs need not be disjoint. + + Parameters + ---------- + graphs : iterable + Iterable of NetworkX graphs + + Returns + ------- + C : A graph with the same type as the first graph in list + + Raises + ------ + ValueError + If `graphs` is an empty list. + + NetworkXError + In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. + + Examples + -------- + >>> G1 = nx.Graph([(1, 2), (2, 3)]) + >>> G2 = nx.Graph([(3, 4), (5, 6)]) + >>> C = nx.compose_all([G1, G2]) + >>> list(C.nodes()) + [1, 2, 3, 4, 5, 6] + >>> list(C.edges()) + [(1, 2), (2, 3), (3, 4), (5, 6)] + + Notes + ----- + For operating on mixed type graphs, they should be converted to the same type. + + Graph, edge, and node attributes are propagated to the union graph. + If a graph attribute is present in multiple graphs, then the value + from the last graph in the list with that attribute is used. + """ + R = None + + # add graph attributes, H attributes take precedent over G attributes + for i, G in enumerate(graphs): + if i == 0: + # create new graph + R = G.__class__() + elif G.is_directed() != R.is_directed(): + raise nx.NetworkXError("All graphs must be directed or undirected.") + elif G.is_multigraph() != R.is_multigraph(): + raise nx.NetworkXError("All graphs must be graphs or multigraphs.") + + R.graph.update(G.graph) + R.add_nodes_from(G.nodes(data=True)) + R.add_edges_from( + G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) + ) + + if R is None: + raise ValueError("cannot apply compose_all to an empty list") + + return R + + +@nx._dispatchable(graphs="[graphs]", returns_graph=True) +def intersection_all(graphs): + """Returns a new graph that contains only the nodes and the edges that exist in + all graphs. + + Parameters + ---------- + graphs : iterable + Iterable of NetworkX graphs + + Returns + ------- + R : A new graph with the same type as the first graph in list + + Raises + ------ + ValueError + If `graphs` is an empty list. + + NetworkXError + In case of mixed type graphs, like MultiGraph and Graph, or directed and undirected graphs. + + Notes + ----- + For operating on mixed type graphs, they should be converted to the same type. + + Attributes from the graph, nodes, and edges are not copied to the new + graph. + + The resulting graph can be updated with attributes if desired. + For example, code which adds the minimum attribute for each node across all + graphs could work:: + + >>> g = nx.Graph() + >>> g.add_node(0, capacity=4) + >>> g.add_node(1, capacity=3) + >>> g.add_edge(0, 1) + + >>> h = g.copy() + >>> h.nodes[0]["capacity"] = 2 + + >>> gh = nx.intersection_all([g, h]) + + >>> new_node_attr = { + ... n: min(*(anyG.nodes[n].get("capacity", float("inf")) for anyG in [g, h])) + ... for n in gh + ... } + >>> nx.set_node_attributes(gh, new_node_attr, "new_capacity") + >>> gh.nodes(data=True) + NodeDataView({0: {'new_capacity': 2}, 1: {'new_capacity': 3}}) + + Examples + -------- + >>> G1 = nx.Graph([(1, 2), (2, 3)]) + >>> G2 = nx.Graph([(2, 3), (3, 4)]) + >>> R = nx.intersection_all([G1, G2]) + >>> list(R.nodes()) + [2, 3] + >>> list(R.edges()) + [(2, 3)] + + """ + R = None + + for i, G in enumerate(graphs): + G_nodes_set = set(G.nodes) + G_edges_set = set(G.edges) + if not G.is_directed(): + if G.is_multigraph(): + G_edges_set.update((v, u, k) for u, v, k in list(G_edges_set)) + else: + G_edges_set.update((v, u) for u, v in list(G_edges_set)) + if i == 0: + # create new graph + R = G.__class__() + node_intersection = G_nodes_set + edge_intersection = G_edges_set + elif G.is_directed() != R.is_directed(): + raise nx.NetworkXError("All graphs must be directed or undirected.") + elif G.is_multigraph() != R.is_multigraph(): + raise nx.NetworkXError("All graphs must be graphs or multigraphs.") + else: + node_intersection &= G_nodes_set + edge_intersection &= G_edges_set + + if R is None: + raise ValueError("cannot apply intersection_all to an empty list") + + R.add_nodes_from(node_intersection) + R.add_edges_from(edge_intersection) + + return R diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/binary.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/binary.py new file mode 100644 index 0000000000000000000000000000000000000000..c1212927a6b855f85d5464bb349b0c20ed79beb0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/binary.py @@ -0,0 +1,468 @@ +""" +Operations on graphs including union, intersection, difference. +""" + +import networkx as nx + +__all__ = [ + "union", + "compose", + "disjoint_union", + "intersection", + "difference", + "symmetric_difference", + "full_join", +] +_G_H = {"G": 0, "H": 1} + + +@nx._dispatchable(graphs=_G_H, preserve_all_attrs=True, returns_graph=True) +def union(G, H, rename=()): + """Combine graphs G and H. The names of nodes must be unique. + + A name collision between the graphs will raise an exception. + + A renaming facility is provided to avoid name collisions. + + + Parameters + ---------- + G, H : graph + A NetworkX graph + + rename : iterable , optional + Node names of G and H can be changed by specifying the tuple + rename=('G-','H-') (for example). Node "u" in G is then renamed + "G-u" and "v" in H is renamed "H-v". + + Returns + ------- + U : A union graph with the same type as G. + + See Also + -------- + compose + :func:`~networkx.Graph.update` + disjoint_union + + Notes + ----- + To combine graphs that have common nodes, consider compose(G, H) + or the method, Graph.update(). + + disjoint_union() is similar to union() except that it avoids name clashes + by relabeling the nodes with sequential integers. + + Edge and node attributes are propagated from G and H to the union graph. + Graph attributes are also propagated, but if they are present in both G and H, + then the value from H is used. + + Examples + -------- + >>> from pprint import pprint + >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) + >>> H = nx.Graph([(0, 1), (0, 3), (1, 3), (1, 2)]) + >>> U = nx.union(G, H, rename=("G", "H")) + >>> U.nodes + NodeView(('G0', 'G1', 'G2', 'H0', 'H1', 'H3', 'H2')) + >>> edgelist = list(U.edges) + >>> pprint(edgelist) + [('G0', 'G1'), + ('G0', 'G2'), + ('G1', 'G2'), + ('H0', 'H1'), + ('H0', 'H3'), + ('H1', 'H3'), + ('H1', 'H2')] + + + """ + return nx.union_all([G, H], rename) + + +@nx._dispatchable(graphs=_G_H, preserve_all_attrs=True, returns_graph=True) +def disjoint_union(G, H): + """Combine graphs G and H. The nodes are assumed to be unique (disjoint). + + This algorithm automatically relabels nodes to avoid name collisions. + + Parameters + ---------- + G,H : graph + A NetworkX graph + + Returns + ------- + U : A union graph with the same type as G. + + See Also + -------- + union + compose + :func:`~networkx.Graph.update` + + Notes + ----- + A new graph is created, of the same class as G. It is recommended + that G and H be either both directed or both undirected. + + The nodes of G are relabeled 0 to len(G)-1, and the nodes of H are + relabeled len(G) to len(G)+len(H)-1. + + Renumbering forces G and H to be disjoint, so no exception is ever raised for a name collision. + To preserve the check for common nodes, use union(). + + Edge and node attributes are propagated from G and H to the union graph. + Graph attributes are also propagated, but if they are present in both G and H, + then the value from H is used. + + To combine graphs that have common nodes, consider compose(G, H) + or the method, Graph.update(). + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) + >>> H = nx.Graph([(0, 3), (1, 2), (2, 3)]) + >>> G.nodes[0]["key1"] = 5 + >>> H.nodes[0]["key2"] = 10 + >>> U = nx.disjoint_union(G, H) + >>> U.nodes(data=True) + NodeDataView({0: {'key1': 5}, 1: {}, 2: {}, 3: {'key2': 10}, 4: {}, 5: {}, 6: {}}) + >>> U.edges + EdgeView([(0, 1), (0, 2), (1, 2), (3, 4), (4, 6), (5, 6)]) + """ + return nx.disjoint_union_all([G, H]) + + +@nx._dispatchable(graphs=_G_H, returns_graph=True) +def intersection(G, H): + """Returns a new graph that contains only the nodes and the edges that exist in + both G and H. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H can have different node sets but must be both graphs or both multigraphs. + + Raises + ------ + NetworkXError + If one is a MultiGraph and the other one is a graph. + + Returns + ------- + GH : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. If you want a new graph of the intersection of G and H + with the attributes (including edge data) from G use remove_nodes_from() + as follows + + >>> G = nx.path_graph(3) + >>> H = nx.path_graph(5) + >>> R = G.copy() + >>> R.remove_nodes_from(n for n in G if n not in H) + >>> R.remove_edges_from(e for e in G.edges if e not in H.edges) + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (1, 2)]) + >>> H = nx.Graph([(0, 3), (1, 2), (2, 3)]) + >>> R = nx.intersection(G, H) + >>> R.nodes + NodeView((0, 1, 2)) + >>> R.edges + EdgeView([(1, 2)]) + """ + return nx.intersection_all([G, H]) + + +@nx._dispatchable(graphs=_G_H, returns_graph=True) +def difference(G, H): + """Returns a new graph that contains the edges that exist in G but not in H. + + The node sets of H and G must be the same. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H must have the same node sets. + + Returns + ------- + D : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. If you want a new graph of the difference of G and H with + the attributes (including edge data) from G use remove_nodes_from() + as follows: + + >>> G = nx.path_graph(3) + >>> H = nx.path_graph(5) + >>> R = G.copy() + >>> R.remove_nodes_from(n for n in G if n in H) + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3)]) + >>> H = nx.Graph([(0, 1), (1, 2), (0, 3)]) + >>> R = nx.difference(G, H) + >>> R.nodes + NodeView((0, 1, 2, 3)) + >>> R.edges + EdgeView([(0, 2), (1, 3)]) + """ + # create new graph + if not G.is_multigraph() == H.is_multigraph(): + raise nx.NetworkXError("G and H must both be graphs or multigraphs.") + R = nx.create_empty_copy(G, with_data=False) + + if set(G) != set(H): + raise nx.NetworkXError("Node sets of graphs not equal") + + if G.is_multigraph(): + edges = G.edges(keys=True) + else: + edges = G.edges() + for e in edges: + if not H.has_edge(*e): + R.add_edge(*e) + return R + + +@nx._dispatchable(graphs=_G_H, returns_graph=True) +def symmetric_difference(G, H): + """Returns new graph with edges that exist in either G or H but not both. + + The node sets of H and G must be the same. + + Parameters + ---------- + G,H : graph + A NetworkX graph. G and H must have the same node sets. + + Returns + ------- + D : A new graph with the same type as G. + + Notes + ----- + Attributes from the graph, nodes, and edges are not copied to the new + graph. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2), (1, 2), (1, 3)]) + >>> H = nx.Graph([(0, 1), (1, 2), (0, 3)]) + >>> R = nx.symmetric_difference(G, H) + >>> R.nodes + NodeView((0, 1, 2, 3)) + >>> R.edges + EdgeView([(0, 2), (0, 3), (1, 3)]) + """ + # create new graph + if not G.is_multigraph() == H.is_multigraph(): + raise nx.NetworkXError("G and H must both be graphs or multigraphs.") + R = nx.create_empty_copy(G, with_data=False) + + if set(G) != set(H): + raise nx.NetworkXError("Node sets of graphs not equal") + + gnodes = set(G) # set of nodes in G + hnodes = set(H) # set of nodes in H + nodes = gnodes.symmetric_difference(hnodes) + R.add_nodes_from(nodes) + + if G.is_multigraph(): + edges = G.edges(keys=True) + else: + edges = G.edges() + # we could copy the data here but then this function doesn't + # match intersection and difference + for e in edges: + if not H.has_edge(*e): + R.add_edge(*e) + + if H.is_multigraph(): + edges = H.edges(keys=True) + else: + edges = H.edges() + for e in edges: + if not G.has_edge(*e): + R.add_edge(*e) + return R + + +@nx._dispatchable(graphs=_G_H, preserve_all_attrs=True, returns_graph=True) +def compose(G, H): + """Compose graph G with H by combining nodes and edges into a single graph. + + The node sets and edges sets do not need to be disjoint. + + Composing preserves the attributes of nodes and edges. + Attribute values from H take precedent over attribute values from G. + + Parameters + ---------- + G, H : graph + A NetworkX graph + + Returns + ------- + C: A new graph with the same type as G + + See Also + -------- + :func:`~networkx.Graph.update` + union + disjoint_union + + Notes + ----- + It is recommended that G and H be either both directed or both undirected. + + For MultiGraphs, the edges are identified by incident nodes AND edge-key. + This can cause surprises (i.e., edge `(1, 2)` may or may not be the same + in two graphs) if you use MultiGraph without keeping track of edge keys. + + If combining the attributes of common nodes is not desired, consider union(), + which raises an exception for name collisions. + + Examples + -------- + >>> G = nx.Graph([(0, 1), (0, 2)]) + >>> H = nx.Graph([(0, 1), (1, 2)]) + >>> R = nx.compose(G, H) + >>> R.nodes + NodeView((0, 1, 2)) + >>> R.edges + EdgeView([(0, 1), (0, 2), (1, 2)]) + + By default, the attributes from `H` take precedent over attributes from `G`. + If you prefer another way of combining attributes, you can update them after the compose operation: + + >>> G = nx.Graph([(0, 1, {"weight": 2.0}), (3, 0, {"weight": 100.0})]) + >>> H = nx.Graph([(0, 1, {"weight": 10.0}), (1, 2, {"weight": -1.0})]) + >>> nx.set_node_attributes(G, {0: "dark", 1: "light", 3: "black"}, name="color") + >>> nx.set_node_attributes(H, {0: "green", 1: "orange", 2: "yellow"}, name="color") + >>> GcomposeH = nx.compose(G, H) + + Normally, color attribute values of nodes of GcomposeH come from H. We can workaround this as follows: + + >>> node_data = { + ... n: G.nodes[n]["color"] + " " + H.nodes[n]["color"] + ... for n in G.nodes & H.nodes + ... } + >>> nx.set_node_attributes(GcomposeH, node_data, "color") + >>> print(GcomposeH.nodes[0]["color"]) + dark green + + >>> print(GcomposeH.nodes[3]["color"]) + black + + Similarly, we can update edge attributes after the compose operation in a way we prefer: + + >>> edge_data = { + ... e: G.edges[e]["weight"] * H.edges[e]["weight"] for e in G.edges & H.edges + ... } + >>> nx.set_edge_attributes(GcomposeH, edge_data, "weight") + >>> print(GcomposeH.edges[(0, 1)]["weight"]) + 20.0 + + >>> print(GcomposeH.edges[(3, 0)]["weight"]) + 100.0 + """ + return nx.compose_all([G, H]) + + +@nx._dispatchable(graphs=_G_H, preserve_all_attrs=True, returns_graph=True) +def full_join(G, H, rename=(None, None)): + """Returns the full join of graphs G and H. + + Full join is the union of G and H in which all edges between + G and H are added. + The node sets of G and H must be disjoint, + otherwise an exception is raised. + + Parameters + ---------- + G, H : graph + A NetworkX graph + + rename : tuple , default=(None, None) + Node names of G and H can be changed by specifying the tuple + rename=('G-','H-') (for example). Node "u" in G is then renamed + "G-u" and "v" in H is renamed "H-v". + + Returns + ------- + U : The full join graph with the same type as G. + + Notes + ----- + It is recommended that G and H be either both directed or both undirected. + + If G is directed, then edges from G to H are added as well as from H to G. + + Note that full_join() does not produce parallel edges for MultiGraphs. + + The full join operation of graphs G and H is the same as getting + their complement, performing a disjoint union, and finally getting + the complement of the resulting graph. + + Graph, edge, and node attributes are propagated from G and H + to the union graph. If a graph attribute is present in both + G and H the value from H is used. + + Examples + -------- + >>> from pprint import pprint + >>> G = nx.Graph([(0, 1), (0, 2)]) + >>> H = nx.Graph([(3, 4)]) + >>> R = nx.full_join(G, H, rename=("G", "H")) + >>> R.nodes + NodeView(('G0', 'G1', 'G2', 'H3', 'H4')) + >>> edgelist = list(R.edges) + >>> pprint(edgelist) + [('G0', 'G1'), + ('G0', 'G2'), + ('G0', 'H3'), + ('G0', 'H4'), + ('G1', 'H3'), + ('G1', 'H4'), + ('G2', 'H3'), + ('G2', 'H4'), + ('H3', 'H4')] + + See Also + -------- + union + disjoint_union + """ + R = union(G, H, rename) + + def add_prefix(graph, prefix): + if prefix is None: + return graph + + def label(x): + return f"{prefix}{x}" + + return nx.relabel_nodes(graph, label) + + G = add_prefix(G, rename[0]) + H = add_prefix(H, rename[1]) + + for i in G: + for j in H: + R.add_edge(i, j) + if R.is_directed(): + for i in H: + for j in G: + R.add_edge(i, j) + + return R diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/product.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/product.py new file mode 100644 index 0000000000000000000000000000000000000000..28ca78bf4deb45ffa422d2792b966adfa112692f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/product.py @@ -0,0 +1,633 @@ +""" +Graph products. +""" + +from itertools import product + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = [ + "tensor_product", + "cartesian_product", + "lexicographic_product", + "strong_product", + "power", + "rooted_product", + "corona_product", + "modular_product", +] +_G_H = {"G": 0, "H": 1} + + +def _dict_product(d1, d2): + return {k: (d1.get(k), d2.get(k)) for k in set(d1) | set(d2)} + + +# Generators for producing graph products +def _node_product(G, H): + for u, v in product(G, H): + yield ((u, v), _dict_product(G.nodes[u], H.nodes[v])) + + +def _directed_edges_cross_edges(G, H): + if not G.is_multigraph() and not H.is_multigraph(): + for u, v, c in G.edges(data=True): + for x, y, d in H.edges(data=True): + yield (u, x), (v, y), _dict_product(c, d) + if not G.is_multigraph() and H.is_multigraph(): + for u, v, c in G.edges(data=True): + for x, y, k, d in H.edges(data=True, keys=True): + yield (u, x), (v, y), k, _dict_product(c, d) + if G.is_multigraph() and not H.is_multigraph(): + for u, v, k, c in G.edges(data=True, keys=True): + for x, y, d in H.edges(data=True): + yield (u, x), (v, y), k, _dict_product(c, d) + if G.is_multigraph() and H.is_multigraph(): + for u, v, j, c in G.edges(data=True, keys=True): + for x, y, k, d in H.edges(data=True, keys=True): + yield (u, x), (v, y), (j, k), _dict_product(c, d) + + +def _undirected_edges_cross_edges(G, H): + if not G.is_multigraph() and not H.is_multigraph(): + for u, v, c in G.edges(data=True): + for x, y, d in H.edges(data=True): + yield (v, x), (u, y), _dict_product(c, d) + if not G.is_multigraph() and H.is_multigraph(): + for u, v, c in G.edges(data=True): + for x, y, k, d in H.edges(data=True, keys=True): + yield (v, x), (u, y), k, _dict_product(c, d) + if G.is_multigraph() and not H.is_multigraph(): + for u, v, k, c in G.edges(data=True, keys=True): + for x, y, d in H.edges(data=True): + yield (v, x), (u, y), k, _dict_product(c, d) + if G.is_multigraph() and H.is_multigraph(): + for u, v, j, c in G.edges(data=True, keys=True): + for x, y, k, d in H.edges(data=True, keys=True): + yield (v, x), (u, y), (j, k), _dict_product(c, d) + + +def _edges_cross_nodes(G, H): + if G.is_multigraph(): + for u, v, k, d in G.edges(data=True, keys=True): + for x in H: + yield (u, x), (v, x), k, d + else: + for u, v, d in G.edges(data=True): + for x in H: + if H.is_multigraph(): + yield (u, x), (v, x), None, d + else: + yield (u, x), (v, x), d + + +def _nodes_cross_edges(G, H): + if H.is_multigraph(): + for x in G: + for u, v, k, d in H.edges(data=True, keys=True): + yield (x, u), (x, v), k, d + else: + for x in G: + for u, v, d in H.edges(data=True): + if G.is_multigraph(): + yield (x, u), (x, v), None, d + else: + yield (x, u), (x, v), d + + +def _edges_cross_nodes_and_nodes(G, H): + if G.is_multigraph(): + for u, v, k, d in G.edges(data=True, keys=True): + for x in H: + for y in H: + yield (u, x), (v, y), k, d + else: + for u, v, d in G.edges(data=True): + for x in H: + for y in H: + if H.is_multigraph(): + yield (u, x), (v, y), None, d + else: + yield (u, x), (v, y), d + + +def _init_product_graph(G, H): + if G.is_directed() != H.is_directed(): + msg = "G and H must be both directed or both undirected" + raise nx.NetworkXError(msg) + if G.is_multigraph() or H.is_multigraph(): + GH = nx.MultiGraph() + else: + GH = nx.Graph() + if G.is_directed(): + GH = GH.to_directed() + return GH + + +@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) +def tensor_product(G, H): + r"""Returns the tensor product of G and H. + + The tensor product $P$ of the graphs $G$ and $H$ has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + $P$ has an edge $((u,v), (x,y))$ if and only if $(u,x)$ is an edge in $G$ + and $(v,y)$ is an edge in $H$. + + Tensor product is sometimes also referred to as the categorical product, + direct product, cardinal product or conjunction. + + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The tensor product of G and H. P will be a multi-graph if either G + or H is a multi-graph, will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + Examples + -------- + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0, a1=True) + >>> H.add_node("a", a2="Spam") + >>> P = nx.tensor_product(G, H) + >>> list(P) + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G, H) + GH.add_nodes_from(_node_product(G, H)) + GH.add_edges_from(_directed_edges_cross_edges(G, H)) + if not GH.is_directed(): + GH.add_edges_from(_undirected_edges_cross_edges(G, H)) + return GH + + +@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) +def cartesian_product(G, H): + r"""Returns the Cartesian product of G and H. + + The Cartesian product $P$ of the graphs $G$ and $H$ has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + $P$ has an edge $((u,v),(x,y))$ if and only if either $u$ is equal to $x$ + and both $v$ and $y$ are adjacent in $H$ or if $v$ is equal to $y$ and + both $u$ and $x$ are adjacent in $G$. + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + Examples + -------- + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0, a1=True) + >>> H.add_node("a", a2="Spam") + >>> P = nx.cartesian_product(G, H) + >>> list(P) + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G, H) + GH.add_nodes_from(_node_product(G, H)) + GH.add_edges_from(_edges_cross_nodes(G, H)) + GH.add_edges_from(_nodes_cross_edges(G, H)) + return GH + + +@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) +def lexicographic_product(G, H): + r"""Returns the lexicographic product of G and H. + + The lexicographical product $P$ of the graphs $G$ and $H$ has a node set + that is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + $P$ has an edge $((u,v), (x,y))$ if and only if $(u,v)$ is an edge in $G$ + or $u==v$ and $(x,y)$ is an edge in $H$. + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + Examples + -------- + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0, a1=True) + >>> H.add_node("a", a2="Spam") + >>> P = nx.lexicographic_product(G, H) + >>> list(P) + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G, H) + GH.add_nodes_from(_node_product(G, H)) + # Edges in G regardless of H designation + GH.add_edges_from(_edges_cross_nodes_and_nodes(G, H)) + # For each x in G, only if there is an edge in H + GH.add_edges_from(_nodes_cross_edges(G, H)) + return GH + + +@nx._dispatchable(graphs=_G_H, preserve_node_attrs=True, returns_graph=True) +def strong_product(G, H): + r"""Returns the strong product of G and H. + + The strong product $P$ of the graphs $G$ and $H$ has a node set that + is the Cartesian product of the node sets, $V(P)=V(G) \times V(H)$. + $P$ has an edge $((u,x), (v,y))$ if any of the following conditions + are met: + + - $u=v$ and $(x,y)$ is an edge in $H$ + - $x=y$ and $(u,v)$ is an edge in $G$ + - $(u,v)$ is an edge in $G$ and $(x,y)$ is an edge in $H$ + + Parameters + ---------- + G, H: graphs + Networkx graphs. + + Returns + ------- + P: NetworkX graph + The Cartesian product of G and H. P will be a multi-graph if either G + or H is a multi-graph. Will be a directed if G and H are directed, + and undirected if G and H are undirected. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Notes + ----- + Node attributes in P are two-tuple of the G and H node attributes. + Missing attributes are assigned None. + + Examples + -------- + >>> G = nx.Graph() + >>> H = nx.Graph() + >>> G.add_node(0, a1=True) + >>> H.add_node("a", a2="Spam") + >>> P = nx.strong_product(G, H) + >>> list(P) + [(0, 'a')] + + Edge attributes and edge keys (for multigraphs) are also copied to the + new product graph + """ + GH = _init_product_graph(G, H) + GH.add_nodes_from(_node_product(G, H)) + GH.add_edges_from(_nodes_cross_edges(G, H)) + GH.add_edges_from(_edges_cross_nodes(G, H)) + GH.add_edges_from(_directed_edges_cross_edges(G, H)) + if not GH.is_directed(): + GH.add_edges_from(_undirected_edges_cross_edges(G, H)) + return GH + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def power(G, k): + """Returns the specified power of a graph. + + The $k$th power of a simple graph $G$, denoted $G^k$, is a + graph on the same set of nodes in which two distinct nodes $u$ and + $v$ are adjacent in $G^k$ if and only if the shortest path + distance between $u$ and $v$ in $G$ is at most $k$. + + Parameters + ---------- + G : graph + A NetworkX simple graph object. + + k : positive integer + The power to which to raise the graph `G`. + + Returns + ------- + NetworkX simple graph + `G` to the power `k`. + + Raises + ------ + ValueError + If the exponent `k` is not positive. + + NetworkXNotImplemented + If `G` is not a simple graph. + + Examples + -------- + The number of edges will never decrease when taking successive + powers: + + >>> G = nx.path_graph(4) + >>> list(nx.power(G, 2).edges) + [(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)] + >>> list(nx.power(G, 3).edges) + [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] + + The `k` th power of a cycle graph on *n* nodes is the complete graph + on *n* nodes, if `k` is at least ``n // 2``: + + >>> G = nx.cycle_graph(5) + >>> H = nx.complete_graph(5) + >>> nx.is_isomorphic(nx.power(G, 2), H) + True + >>> G = nx.cycle_graph(8) + >>> H = nx.complete_graph(8) + >>> nx.is_isomorphic(nx.power(G, 4), H) + True + + References + ---------- + .. [1] J. A. Bondy, U. S. R. Murty, *Graph Theory*. Springer, 2008. + + Notes + ----- + This definition of "power graph" comes from Exercise 3.1.6 of + *Graph Theory* by Bondy and Murty [1]_. + + """ + if k <= 0: + raise ValueError("k must be a positive integer") + H = nx.Graph() + H.add_nodes_from(G) + # update BFS code to ignore self loops. + for n in G: + seen = {} # level (number of hops) when seen in BFS + level = 1 # the current level + nextlevel = G[n] + while nextlevel: + thislevel = nextlevel # advance to next level + nextlevel = {} # and start a new list (fringe) + for v in thislevel: + if v == n: # avoid self loop + continue + if v not in seen: + seen[v] = level # set the level of vertex v + nextlevel.update(G[v]) # add neighbors of v + if k <= level: + break + level += 1 + H.add_edges_from((n, nbr) for nbr in seen) + return H + + +@not_implemented_for("multigraph") +@nx._dispatchable(graphs=_G_H, returns_graph=True) +def rooted_product(G, H, root): + """Return the rooted product of graphs G and H rooted at root in H. + + A new graph is constructed representing the rooted product of + the inputted graphs, G and H, with a root in H. + A rooted product duplicates H for each nodes in G with the root + of H corresponding to the node in G. Nodes are renamed as the direct + product of G and H. The result is a subgraph of the cartesian product. + + Parameters + ---------- + G,H : graph + A NetworkX graph + root : node + A node in H + + Returns + ------- + R : The rooted product of G and H with a specified root in H + + Notes + ----- + The nodes of R are the Cartesian Product of the nodes of G and H. + The nodes of G and H are not relabeled. + """ + if root not in H: + raise nx.NodeNotFound("root must be a vertex in H") + + R = nx.Graph() + R.add_nodes_from(product(G, H)) + + R.add_edges_from(((e[0], root), (e[1], root)) for e in G.edges()) + R.add_edges_from(((g, e[0]), (g, e[1])) for g in G for e in H.edges()) + + return R + + +@not_implemented_for("directed") +@not_implemented_for("multigraph") +@nx._dispatchable(graphs=_G_H, returns_graph=True) +def corona_product(G, H): + r"""Returns the Corona product of G and H. + + The corona product of $G$ and $H$ is the graph $C = G \circ H$ obtained by + taking one copy of $G$, called the center graph, $|V(G)|$ copies of $H$, + called the outer graph, and making the $i$-th vertex of $G$ adjacent to + every vertex of the $i$-th copy of $H$, where $1 ≤ i ≤ |V(G)|$. + + Parameters + ---------- + G, H: NetworkX graphs + The graphs to take the carona product of. + `G` is the center graph and `H` is the outer graph + + Returns + ------- + C: NetworkX graph + The Corona product of G and H. + + Raises + ------ + NetworkXError + If G and H are not both directed or both undirected. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> H = nx.path_graph(2) + >>> C = nx.corona_product(G, H) + >>> list(C) + [0, 1, 2, 3, (0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] + >>> print(C) + Graph with 12 nodes and 16 edges + + References + ---------- + [1] M. Tavakoli, F. Rahbarnia, and A. R. Ashrafi, + "Studying the corona product of graphs under some graph invariants," + Transactions on Combinatorics, vol. 3, no. 3, pp. 43–49, Sep. 2014, + doi: 10.22108/toc.2014.5542. + [2] A. Faraji, "Corona Product in Graph Theory," Ali Faraji, May 11, 2021. + https://blog.alifaraji.ir/math/graph-theory/corona-product.html (accessed Dec. 07, 2021). + """ + GH = _init_product_graph(G, H) + GH.add_nodes_from(G) + GH.add_edges_from(G.edges) + + for G_node in G: + # copy nodes of H in GH, call it H_i + GH.add_nodes_from((G_node, v) for v in H) + + # copy edges of H_i based on H + GH.add_edges_from( + ((G_node, e0), (G_node, e1), d) for e0, e1, d in H.edges.data() + ) + + # creating new edges between H_i and a G's node + GH.add_edges_from((G_node, (G_node, H_node)) for H_node in H) + + return GH + + +@nx._dispatchable( + graphs=_G_H, preserve_edge_attrs=True, preserve_node_attrs=True, returns_graph=True +) +def modular_product(G, H): + r"""Returns the Modular product of G and H. + + The modular product of `G` and `H` is the graph $M = G \nabla H$, + consisting of the node set $V(M) = V(G) \times V(H)$ that is the Cartesian + product of the node sets of `G` and `H`. Further, M contains an edge ((u, v), (x, y)): + + - if u is adjacent to x in `G` and v is adjacent to y in `H`, or + - if u is not adjacent to x in `G` and v is not adjacent to y in `H`. + + More formally:: + + E(M) = {((u, v), (x, y)) | ((u, x) in E(G) and (v, y) in E(H)) or + ((u, x) not in E(G) and (v, y) not in E(H))} + + Parameters + ---------- + G, H: NetworkX graphs + The graphs to take the modular product of. + + Returns + ------- + M: NetworkX graph + The Modular product of `G` and `H`. + + Raises + ------ + NetworkXNotImplemented + If `G` is not a simple graph. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> H = nx.path_graph(2) + >>> M = nx.modular_product(G, H) + >>> list(M) + [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1), (3, 0), (3, 1)] + >>> print(M) + Graph with 8 nodes and 8 edges + + Notes + ----- + The *modular product* is defined in [1]_ and was first + introduced as the *weak modular product*. + + The modular product reduces the problem of counting isomorphic subgraphs + in `G` and `H` to the problem of counting cliques in M. The subgraphs of + `G` and `H` that are induced by the nodes of a clique in M are + isomorphic [2]_ [3]_. + + References + ---------- + .. [1] R. Hammack, W. Imrich, and S. Klavžar, + "Handbook of Product Graphs", CRC Press, 2011. + + .. [2] H. G. Barrow and R. M. Burstall, + "Subgraph isomorphism, matching relational structures and maximal + cliques", Information Processing Letters, vol. 4, issue 4, pp. 83-84, + 1976, https://doi.org/10.1016/0020-0190(76)90049-1. + + .. [3] V. G. Vizing, "Reduction of the problem of isomorphism and isomorphic + entrance to the task of finding the nondensity of a graph." Proc. Third + All-Union Conference on Problems of Theoretical Cybernetics. 1974. + """ + if G.is_directed() or H.is_directed(): + raise nx.NetworkXNotImplemented( + "Modular product not implemented for directed graphs" + ) + if G.is_multigraph() or H.is_multigraph(): + raise nx.NetworkXNotImplemented( + "Modular product not implemented for multigraphs" + ) + + GH = _init_product_graph(G, H) + GH.add_nodes_from(_node_product(G, H)) + + for u, v, c in G.edges(data=True): + for x, y, d in H.edges(data=True): + GH.add_edge((u, x), (v, y), **_dict_product(c, d)) + GH.add_edge((v, x), (u, y), **_dict_product(c, d)) + + G = nx.complement(G) + H = nx.complement(H) + + for u, v, c in G.edges(data=True): + for x, y, d in H.edges(data=True): + GH.add_edge((u, x), (v, y), **_dict_product(c, d)) + GH.add_edge((v, x), (u, y), **_dict_product(c, d)) + + return GH diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0557a3db076f9b01b9d26af1783d6c6ce6be9024 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_all.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_all.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1594b20cf474de5ffefc4024e993631d867c1a3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_all.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_binary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_binary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17f7bf9df4acd4e8b53095be66d4e324ad0667b1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_binary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_product.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_product.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b7c8c37a34eb7f03b8a942ce7d71ec52ad8d3a3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_product.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_unary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_unary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..851b888d4f8efdacdae5eb3ed52f59c810562ab0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/__pycache__/test_unary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py new file mode 100644 index 0000000000000000000000000000000000000000..c694d35eb687127dc9064286866597fcafc9f5b7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_all.py @@ -0,0 +1,328 @@ +import pytest + +import networkx as nx +from networkx.utils import edges_equal + + +def test_union_all_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + + j = g.copy() + j.graph["name"] = "j" + j.graph["attr"] = "attr" + j.nodes[0]["x"] = 7 + + ghj = nx.union_all([g, h, j], rename=("g", "h", "j")) + assert set(ghj.nodes()) == {"h0", "h1", "g0", "g1", "j0", "j1"} + for n in ghj: + graph, node = n + assert ghj.nodes[n] == eval(graph).nodes[int(node)] + + assert ghj.graph["attr"] == "attr" + assert ghj.graph["name"] == "j" # j graph attributes take precedent + + +def test_intersection_all(): + G = nx.Graph() + H = nx.Graph() + R = nx.Graph(awesome=True) + G.add_nodes_from([1, 2, 3, 4]) + G.add_edge(1, 2) + G.add_edge(2, 3) + H.add_nodes_from([1, 2, 3, 4]) + H.add_edge(2, 3) + H.add_edge(3, 4) + R.add_nodes_from([1, 2, 3, 4]) + R.add_edge(2, 3) + R.add_edge(4, 1) + I = nx.intersection_all([G, H, R]) + assert set(I.nodes()) == {1, 2, 3, 4} + assert sorted(I.edges()) == [(2, 3)] + assert I.graph == {} + + +def test_intersection_all_different_node_sets(): + G = nx.Graph() + H = nx.Graph() + R = nx.Graph() + G.add_nodes_from([1, 2, 3, 4, 6, 7]) + G.add_edge(1, 2) + G.add_edge(2, 3) + G.add_edge(6, 7) + H.add_nodes_from([1, 2, 3, 4]) + H.add_edge(2, 3) + H.add_edge(3, 4) + R.add_nodes_from([1, 2, 3, 4, 8, 9]) + R.add_edge(2, 3) + R.add_edge(4, 1) + R.add_edge(8, 9) + I = nx.intersection_all([G, H, R]) + assert set(I.nodes()) == {1, 2, 3, 4} + assert sorted(I.edges()) == [(2, 3)] + + +def test_intersection_all_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + + gh = nx.intersection_all([g, h]) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == sorted(g.edges()) + + +def test_intersection_all_attributes_different_node_sets(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + g.add_node(2) + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + + gh = nx.intersection_all([g, h]) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == sorted(g.edges()) + + +def test_intersection_all_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.intersection_all([g, h]) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [(0, 1)] + assert sorted(gh.edges(keys=True)) == [(0, 1, 0)] + + +def test_intersection_all_multigraph_attributes_different_node_sets(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + g.add_edge(1, 2, key=1) + g.add_edge(1, 2, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=2) + h.add_edge(0, 1, key=3) + gh = nx.intersection_all([g, h]) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [(0, 1), (0, 1)] + assert sorted(gh.edges(keys=True)) == [(0, 1, 0), (0, 1, 2)] + + +def test_intersection_all_digraph(): + g = nx.DiGraph() + g.add_edges_from([(1, 2), (2, 3)]) + h = nx.DiGraph() + h.add_edges_from([(2, 1), (2, 3)]) + gh = nx.intersection_all([g, h]) + assert sorted(gh.edges()) == [(2, 3)] + + +def test_union_all_and_compose_all(): + K3 = nx.complete_graph(3) + P3 = nx.path_graph(3) + + G1 = nx.DiGraph() + G1.add_edge("A", "B") + G1.add_edge("A", "C") + G1.add_edge("A", "D") + G2 = nx.DiGraph() + G2.add_edge("1", "2") + G2.add_edge("1", "3") + G2.add_edge("1", "4") + + G = nx.union_all([G1, G2]) + H = nx.compose_all([G1, G2]) + assert edges_equal(G.edges(), H.edges(), directed=True) + assert not G.has_edge("A", "1") + pytest.raises(nx.NetworkXError, nx.union, K3, P3) + H1 = nx.union_all([H, G1], rename=("H", "G1")) + assert sorted(H1.nodes()) == [ + "G1A", + "G1B", + "G1C", + "G1D", + "H1", + "H2", + "H3", + "H4", + "HA", + "HB", + "HC", + "HD", + ] + + H2 = nx.union_all([H, G2], rename=("H", "")) + assert sorted(H2.nodes()) == [ + "1", + "2", + "3", + "4", + "H1", + "H2", + "H3", + "H4", + "HA", + "HB", + "HC", + "HD", + ] + + assert not H1.has_edge("NB", "NA") + + G = nx.compose_all([G, G]) + assert edges_equal(G.edges(), H.edges(), directed=True) + + G2 = nx.union_all([G2, G2], rename=("", "copy")) + assert sorted(G2.nodes()) == [ + "1", + "2", + "3", + "4", + "copy1", + "copy2", + "copy3", + "copy4", + ] + + assert sorted(G2.neighbors("copy4")) == [] + assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"] + assert len(G) == 8 + assert nx.number_of_edges(G) == 6 + + E = nx.disjoint_union_all([G, G]) + assert len(E) == 16 + assert nx.number_of_edges(E) == 12 + + E = nx.disjoint_union_all([G1, G2]) + assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + + G1 = nx.DiGraph() + G1.add_edge("A", "B") + G2 = nx.DiGraph() + G2.add_edge(1, 2) + G3 = nx.DiGraph() + G3.add_edge(11, 22) + G4 = nx.union_all([G1, G2, G3], rename=("G1", "G2", "G3")) + assert sorted(G4.nodes()) == ["G1A", "G1B", "G21", "G22", "G311", "G322"] + + +def test_union_all_multigraph(): + G = nx.MultiGraph() + G.add_edge(1, 2, key=0) + G.add_edge(1, 2, key=1) + H = nx.MultiGraph() + H.add_edge(3, 4, key=0) + H.add_edge(3, 4, key=1) + GH = nx.union_all([G, H]) + assert set(GH) == set(G) | set(H) + assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True)) + + +def test_input_output(): + l = [nx.Graph([(1, 2)]), nx.Graph([(3, 4)], awesome=True)] + U = nx.disjoint_union_all(l) + assert len(l) == 2 + assert U.graph["awesome"] + C = nx.compose_all(l) + assert len(l) == 2 + l = [nx.Graph([(1, 2)]), nx.Graph([(1, 2)])] + R = nx.intersection_all(l) + assert len(l) == 2 + + +def test_mixed_type_union(): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + H = nx.MultiGraph() + I = nx.Graph() + U = nx.union_all([G, H, I]) + with pytest.raises(nx.NetworkXError): + X = nx.Graph() + Y = nx.DiGraph() + XY = nx.union_all([X, Y]) + + +def test_mixed_type_disjoint_union(): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + H = nx.MultiGraph() + I = nx.Graph() + U = nx.disjoint_union_all([G, H, I]) + with pytest.raises(nx.NetworkXError): + X = nx.Graph() + Y = nx.DiGraph() + XY = nx.disjoint_union_all([X, Y]) + + +def test_mixed_type_intersection(): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + H = nx.MultiGraph() + I = nx.Graph() + U = nx.intersection_all([G, H, I]) + with pytest.raises(nx.NetworkXError): + X = nx.Graph() + Y = nx.DiGraph() + XY = nx.intersection_all([X, Y]) + + +def test_mixed_type_compose(): + with pytest.raises(nx.NetworkXError): + G = nx.Graph() + H = nx.MultiGraph() + I = nx.Graph() + U = nx.compose_all([G, H, I]) + with pytest.raises(nx.NetworkXError): + X = nx.Graph() + Y = nx.DiGraph() + XY = nx.compose_all([X, Y]) + + +def test_empty_union(): + with pytest.raises(ValueError): + nx.union_all([]) + + +def test_empty_disjoint_union(): + with pytest.raises(ValueError): + nx.disjoint_union_all([]) + + +def test_empty_compose_all(): + with pytest.raises(ValueError): + nx.compose_all([]) + + +def test_empty_intersection_all(): + with pytest.raises(ValueError): + nx.intersection_all([]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py new file mode 100644 index 0000000000000000000000000000000000000000..70a199d52ad984e2d315fde8a0fb72d37e77849c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_binary.py @@ -0,0 +1,451 @@ +import pytest + +import networkx as nx +from networkx.utils import edges_equal + + +def test_union_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + + gh = nx.union(g, h, rename=("g", "h")) + assert set(gh.nodes()) == {"h0", "h1", "g0", "g1"} + for n in gh: + graph, node = n + assert gh.nodes[n] == eval(graph).nodes[int(node)] + + assert gh.graph["attr"] == "attr" + assert gh.graph["name"] == "h" # h graph attributes take precedent + + +def test_intersection(): + G = nx.Graph() + H = nx.Graph() + G.add_nodes_from([1, 2, 3, 4]) + G.add_edge(1, 2) + G.add_edge(2, 3) + H.add_nodes_from([1, 2, 3, 4]) + H.add_edge(2, 3) + H.add_edge(3, 4) + I = nx.intersection(G, H) + assert set(I.nodes()) == {1, 2, 3, 4} + assert sorted(I.edges()) == [(2, 3)] + + +def test_intersection_node_sets_different(): + G = nx.Graph() + H = nx.Graph() + G.add_nodes_from([1, 2, 3, 4, 7]) + G.add_edge(1, 2) + G.add_edge(2, 3) + H.add_nodes_from([1, 2, 3, 4, 5, 6]) + H.add_edge(2, 3) + H.add_edge(3, 4) + H.add_edge(5, 6) + I = nx.intersection(G, H) + assert set(I.nodes()) == {1, 2, 3, 4} + assert sorted(I.edges()) == [(2, 3)] + + +def test_intersection_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + gh = nx.intersection(g, h) + + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == sorted(g.edges()) + + +def test_intersection_attributes_node_sets_different(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_node(2, x=3) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + h.remove_node(2) + + gh = nx.intersection(g, h) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == sorted(g.edges()) + + +def test_intersection_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.intersection(g, h) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [(0, 1)] + assert sorted(gh.edges(keys=True)) == [(0, 1, 0)] + + +def test_intersection_multigraph_attributes_node_set_different(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + g.add_edge(0, 2, key=2) + g.add_edge(0, 2, key=1) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.intersection(g, h) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [(0, 1)] + assert sorted(gh.edges(keys=True)) == [(0, 1, 0)] + + +def test_difference(): + G = nx.Graph() + H = nx.Graph() + G.add_nodes_from([1, 2, 3, 4]) + G.add_edge(1, 2) + G.add_edge(2, 3) + H.add_nodes_from([1, 2, 3, 4]) + H.add_edge(2, 3) + H.add_edge(3, 4) + D = nx.difference(G, H) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [(1, 2)] + D = nx.difference(H, G) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [(3, 4)] + D = nx.symmetric_difference(G, H) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [(1, 2), (3, 4)] + + +def test_difference2(): + G = nx.Graph() + H = nx.Graph() + G.add_nodes_from([1, 2, 3, 4]) + H.add_nodes_from([1, 2, 3, 4]) + G.add_edge(1, 2) + H.add_edge(1, 2) + G.add_edge(2, 3) + D = nx.difference(G, H) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [(2, 3)] + D = nx.difference(H, G) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [] + H.add_edge(3, 4) + D = nx.difference(H, G) + assert set(D.nodes()) == {1, 2, 3, 4} + assert sorted(D.edges()) == [(3, 4)] + + +def test_difference_attributes(): + g = nx.Graph() + g.add_node(0, x=4) + g.add_node(1, x=5) + g.add_edge(0, 1, size=5) + g.graph["name"] = "g" + + h = g.copy() + h.graph["name"] = "h" + h.graph["attr"] = "attr" + h.nodes[0]["x"] = 7 + + gh = nx.difference(g, h) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [] + # node and graph data should not be copied over + assert gh.nodes.data() != g.nodes.data() + assert gh.graph != g.graph + + +def test_difference_multigraph_attributes(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.difference(g, h) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == [(0, 1), (0, 1)] + assert sorted(gh.edges(keys=True)) == [(0, 1, 1), (0, 1, 2)] + + +def test_difference_raise(): + G = nx.path_graph(4) + H = nx.path_graph(3) + pytest.raises(nx.NetworkXError, nx.difference, G, H) + pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H) + + +def test_symmetric_difference_multigraph(): + g = nx.MultiGraph() + g.add_edge(0, 1, key=0) + g.add_edge(0, 1, key=1) + g.add_edge(0, 1, key=2) + h = nx.MultiGraph() + h.add_edge(0, 1, key=0) + h.add_edge(0, 1, key=3) + gh = nx.symmetric_difference(g, h) + assert set(gh.nodes()) == set(g.nodes()) + assert set(gh.nodes()) == set(h.nodes()) + assert sorted(gh.edges()) == 3 * [(0, 1)] + assert sorted(sorted(e) for e in gh.edges(keys=True)) == [ + [0, 1, 1], + [0, 1, 2], + [0, 1, 3], + ] + + +def test_union_and_compose(): + K3 = nx.complete_graph(3) + P3 = nx.path_graph(3) + + G1 = nx.DiGraph() + G1.add_edge("A", "B") + G1.add_edge("A", "C") + G1.add_edge("A", "D") + G2 = nx.DiGraph() + G2.add_edge("1", "2") + G2.add_edge("1", "3") + G2.add_edge("1", "4") + + G = nx.union(G1, G2) + H = nx.compose(G1, G2) + assert edges_equal(G.edges(), H.edges(), directed=True) + assert not G.has_edge("A", 1) + pytest.raises(nx.NetworkXError, nx.union, K3, P3) + H1 = nx.union(H, G1, rename=("H", "G1")) + assert sorted(H1.nodes()) == [ + "G1A", + "G1B", + "G1C", + "G1D", + "H1", + "H2", + "H3", + "H4", + "HA", + "HB", + "HC", + "HD", + ] + + H2 = nx.union(H, G2, rename=("H", "")) + assert sorted(H2.nodes()) == [ + "1", + "2", + "3", + "4", + "H1", + "H2", + "H3", + "H4", + "HA", + "HB", + "HC", + "HD", + ] + + assert not H1.has_edge("NB", "NA") + + G = nx.compose(G, G) + assert edges_equal(G.edges(), H.edges(), directed=True) + + G2 = nx.union(G2, G2, rename=("", "copy")) + assert sorted(G2.nodes()) == [ + "1", + "2", + "3", + "4", + "copy1", + "copy2", + "copy3", + "copy4", + ] + + assert sorted(G2.neighbors("copy4")) == [] + assert sorted(G2.neighbors("copy1")) == ["copy2", "copy3", "copy4"] + assert len(G) == 8 + assert nx.number_of_edges(G) == 6 + + E = nx.disjoint_union(G, G) + assert len(E) == 16 + assert nx.number_of_edges(E) == 12 + + E = nx.disjoint_union(G1, G2) + assert sorted(E.nodes()) == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + + G = nx.Graph() + H = nx.Graph() + G.add_nodes_from([(1, {"a1": 1})]) + H.add_nodes_from([(1, {"b1": 1})]) + R = nx.compose(G, H) + assert R.nodes == {1: {"a1": 1, "b1": 1}} + + +def test_union_multigraph(): + G = nx.MultiGraph() + G.add_edge(1, 2, key=0) + G.add_edge(1, 2, key=1) + H = nx.MultiGraph() + H.add_edge(3, 4, key=0) + H.add_edge(3, 4, key=1) + GH = nx.union(G, H) + assert set(GH) == set(G) | set(H) + assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True)) + + +def test_disjoint_union_multigraph(): + G = nx.MultiGraph() + G.add_edge(0, 1, key=0) + G.add_edge(0, 1, key=1) + H = nx.MultiGraph() + H.add_edge(2, 3, key=0) + H.add_edge(2, 3, key=1) + GH = nx.disjoint_union(G, H) + assert set(GH) == set(G) | set(H) + assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True)) + + +def test_compose_multigraph(): + G = nx.MultiGraph() + G.add_edge(1, 2, key=0) + G.add_edge(1, 2, key=1) + H = nx.MultiGraph() + H.add_edge(3, 4, key=0) + H.add_edge(3, 4, key=1) + GH = nx.compose(G, H) + assert set(GH) == set(G) | set(H) + assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True)) + H.add_edge(1, 2, key=2) + GH = nx.compose(G, H) + assert set(GH) == set(G) | set(H) + assert set(GH.edges(keys=True)) == set(G.edges(keys=True)) | set(H.edges(keys=True)) + + +def test_full_join_graph(): + # Simple Graphs + G = nx.Graph() + G.add_node(0) + G.add_edge(1, 2) + H = nx.Graph() + H.add_edge(3, 4) + + U = nx.full_join(G, H) + assert set(U) == set(G) | set(H) + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) + + # Rename + U = nx.full_join(G, H, rename=("g", "h")) + assert set(U) == {"g0", "g1", "g2", "h3", "h4"} + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) + + # Rename graphs with string-like nodes + G = nx.Graph() + G.add_node("a") + G.add_edge("b", "c") + H = nx.Graph() + H.add_edge("d", "e") + + U = nx.full_join(G, H, rename=("g", "h")) + assert set(U) == {"ga", "gb", "gc", "hd", "he"} + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) + + # DiGraphs + G = nx.DiGraph() + G.add_node(0) + G.add_edge(1, 2) + H = nx.DiGraph() + H.add_edge(3, 4) + + U = nx.full_join(G, H) + assert set(U) == set(G) | set(H) + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2 + + # DiGraphs Rename + U = nx.full_join(G, H, rename=("g", "h")) + assert set(U) == {"g0", "g1", "g2", "h3", "h4"} + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2 + + +def test_full_join_multigraph(): + # MultiGraphs + G = nx.MultiGraph() + G.add_node(0) + G.add_edge(1, 2) + H = nx.MultiGraph() + H.add_edge(3, 4) + + U = nx.full_join(G, H) + assert set(U) == set(G) | set(H) + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) + + # MultiGraphs rename + U = nx.full_join(G, H, rename=("g", "h")) + assert set(U) == {"g0", "g1", "g2", "h3", "h4"} + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) + + # MultiDiGraphs + G = nx.MultiDiGraph() + G.add_node(0) + G.add_edge(1, 2) + H = nx.MultiDiGraph() + H.add_edge(3, 4) + + U = nx.full_join(G, H) + assert set(U) == set(G) | set(H) + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2 + + # MultiDiGraphs rename + U = nx.full_join(G, H, rename=("g", "h")) + assert set(U) == {"g0", "g1", "g2", "h3", "h4"} + assert len(U) == len(G) + len(H) + assert len(U.edges()) == len(G.edges()) + len(H.edges()) + len(G) * len(H) * 2 + + +def test_mixed_type_union(): + G = nx.Graph() + H = nx.MultiGraph() + pytest.raises(nx.NetworkXError, nx.union, G, H) + pytest.raises(nx.NetworkXError, nx.disjoint_union, G, H) + pytest.raises(nx.NetworkXError, nx.intersection, G, H) + pytest.raises(nx.NetworkXError, nx.difference, G, H) + pytest.raises(nx.NetworkXError, nx.symmetric_difference, G, H) + pytest.raises(nx.NetworkXError, nx.compose, G, H) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py new file mode 100644 index 0000000000000000000000000000000000000000..8ee54b93012c79531f2732da282072754da82046 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_product.py @@ -0,0 +1,491 @@ +import pytest + +import networkx as nx +from networkx.utils import edges_equal + + +def test_tensor_product_raises(): + with pytest.raises(nx.NetworkXError): + P = nx.tensor_product(nx.DiGraph(), nx.Graph()) + + +def test_tensor_product_null(): + null = nx.null_graph() + empty10 = nx.empty_graph(10) + K3 = nx.complete_graph(3) + K10 = nx.complete_graph(10) + P3 = nx.path_graph(3) + P10 = nx.path_graph(10) + # null graph + G = nx.tensor_product(null, null) + assert nx.is_isomorphic(G, null) + # null_graph X anything = null_graph and v.v. + G = nx.tensor_product(null, empty10) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(null, K3) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(null, K10) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(null, P3) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(null, P10) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(empty10, null) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(K3, null) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(K10, null) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(P3, null) + assert nx.is_isomorphic(G, null) + G = nx.tensor_product(P10, null) + assert nx.is_isomorphic(G, null) + + +def test_tensor_product_size(): + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + K5 = nx.complete_graph(5) + + G = nx.tensor_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.tensor_product(K3, K5) + assert nx.number_of_nodes(G) == 3 * 5 + + +def test_tensor_product_combinations(): + # basic smoke test, more realistic tests would be useful + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.tensor_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.tensor_product(P5, nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.tensor_product(nx.MultiGraph(P5), K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.tensor_product(nx.MultiGraph(P5), nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + + G = nx.tensor_product(nx.DiGraph(P5), nx.DiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + + +def test_tensor_product_classic_result(): + K2 = nx.complete_graph(2) + G = nx.petersen_graph() + G = nx.tensor_product(G, K2) + assert nx.is_isomorphic(G, nx.desargues_graph()) + + G = nx.cycle_graph(5) + G = nx.tensor_product(G, K2) + assert nx.is_isomorphic(G, nx.cycle_graph(10)) + + G = nx.tetrahedral_graph() + G = nx.tensor_product(G, K2) + assert nx.is_isomorphic(G, nx.cubical_graph()) + + +def test_tensor_product_random(): + G = nx.erdos_renyi_graph(10, 2 / 10.0) + H = nx.erdos_renyi_graph(10, 2 / 10.0) + GH = nx.tensor_product(G, H) + + for u_G, u_H in GH.nodes(): + for v_G, v_H in GH.nodes(): + if H.has_edge(u_H, v_H) and G.has_edge(u_G, v_G): + assert GH.has_edge((u_G, u_H), (v_G, v_H)) + else: + assert not GH.has_edge((u_G, u_H), (v_G, v_H)) + + +def test_cartesian_product_multigraph(): + G = nx.MultiGraph() + G.add_edge(1, 2, key=0) + G.add_edge(1, 2, key=1) + H = nx.MultiGraph() + H.add_edge(3, 4, key=0) + H.add_edge(3, 4, key=1) + GH = nx.cartesian_product(G, H) + assert set(GH) == {(1, 3), (2, 3), (2, 4), (1, 4)} + assert {(frozenset([u, v]), k) for u, v, k in GH.edges(keys=True)} == { + (frozenset([u, v]), k) + for u, v, k in [ + ((1, 3), (2, 3), 0), + ((1, 3), (2, 3), 1), + ((1, 3), (1, 4), 0), + ((1, 3), (1, 4), 1), + ((2, 3), (2, 4), 0), + ((2, 3), (2, 4), 1), + ((2, 4), (1, 4), 0), + ((2, 4), (1, 4), 1), + ] + } + + +def test_cartesian_product_raises(): + with pytest.raises(nx.NetworkXError): + P = nx.cartesian_product(nx.DiGraph(), nx.Graph()) + + +def test_cartesian_product_null(): + null = nx.null_graph() + empty10 = nx.empty_graph(10) + K3 = nx.complete_graph(3) + K10 = nx.complete_graph(10) + P3 = nx.path_graph(3) + P10 = nx.path_graph(10) + # null graph + G = nx.cartesian_product(null, null) + assert nx.is_isomorphic(G, null) + # null_graph X anything = null_graph and v.v. + G = nx.cartesian_product(null, empty10) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(null, K3) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(null, K10) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(null, P3) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(null, P10) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(empty10, null) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(K3, null) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(K10, null) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(P3, null) + assert nx.is_isomorphic(G, null) + G = nx.cartesian_product(P10, null) + assert nx.is_isomorphic(G, null) + + +def test_cartesian_product_size(): + # order(GXH)=order(G)*order(H) + K5 = nx.complete_graph(5) + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.cartesian_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + assert nx.number_of_edges(G) == nx.number_of_edges(P5) * nx.number_of_nodes( + K3 + ) + nx.number_of_edges(K3) * nx.number_of_nodes(P5) + G = nx.cartesian_product(K3, K5) + assert nx.number_of_nodes(G) == 3 * 5 + assert nx.number_of_edges(G) == nx.number_of_edges(K5) * nx.number_of_nodes( + K3 + ) + nx.number_of_edges(K3) * nx.number_of_nodes(K5) + + +def test_cartesian_product_classic(): + # test some classic product graphs + P2 = nx.path_graph(2) + P3 = nx.path_graph(3) + # cube = 2-path X 2-path + G = nx.cartesian_product(P2, P2) + G = nx.cartesian_product(P2, G) + assert nx.is_isomorphic(G, nx.cubical_graph()) + + # 3x3 grid + G = nx.cartesian_product(P3, P3) + assert nx.is_isomorphic(G, nx.grid_2d_graph(3, 3)) + + +def test_cartesian_product_random(): + G = nx.erdos_renyi_graph(10, 2 / 10.0) + H = nx.erdos_renyi_graph(10, 2 / 10.0) + GH = nx.cartesian_product(G, H) + + for u_G, u_H in GH.nodes(): + for v_G, v_H in GH.nodes(): + if (u_G == v_G and H.has_edge(u_H, v_H)) or ( + u_H == v_H and G.has_edge(u_G, v_G) + ): + assert GH.has_edge((u_G, u_H), (v_G, v_H)) + else: + assert not GH.has_edge((u_G, u_H), (v_G, v_H)) + + +def test_lexicographic_product_raises(): + with pytest.raises(nx.NetworkXError): + P = nx.lexicographic_product(nx.DiGraph(), nx.Graph()) + + +def test_lexicographic_product_null(): + null = nx.null_graph() + empty10 = nx.empty_graph(10) + K3 = nx.complete_graph(3) + K10 = nx.complete_graph(10) + P3 = nx.path_graph(3) + P10 = nx.path_graph(10) + # null graph + G = nx.lexicographic_product(null, null) + assert nx.is_isomorphic(G, null) + # null_graph X anything = null_graph and v.v. + G = nx.lexicographic_product(null, empty10) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(null, K3) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(null, K10) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(null, P3) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(null, P10) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(empty10, null) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(K3, null) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(K10, null) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(P3, null) + assert nx.is_isomorphic(G, null) + G = nx.lexicographic_product(P10, null) + assert nx.is_isomorphic(G, null) + + +def test_lexicographic_product_size(): + K5 = nx.complete_graph(5) + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.lexicographic_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.lexicographic_product(K3, K5) + assert nx.number_of_nodes(G) == 3 * 5 + + +def test_lexicographic_product_combinations(): + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.lexicographic_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.lexicographic_product(nx.MultiGraph(P5), K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.lexicographic_product(P5, nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.lexicographic_product(nx.MultiGraph(P5), nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + + # No classic easily found classic results for lexicographic product + + +def test_lexicographic_product_random(): + G = nx.erdos_renyi_graph(10, 2 / 10.0) + H = nx.erdos_renyi_graph(10, 2 / 10.0) + GH = nx.lexicographic_product(G, H) + + for u_G, u_H in GH.nodes(): + for v_G, v_H in GH.nodes(): + if G.has_edge(u_G, v_G) or (u_G == v_G and H.has_edge(u_H, v_H)): + assert GH.has_edge((u_G, u_H), (v_G, v_H)) + else: + assert not GH.has_edge((u_G, u_H), (v_G, v_H)) + + +def test_strong_product_raises(): + with pytest.raises(nx.NetworkXError): + P = nx.strong_product(nx.DiGraph(), nx.Graph()) + + +def test_strong_product_null(): + null = nx.null_graph() + empty10 = nx.empty_graph(10) + K3 = nx.complete_graph(3) + K10 = nx.complete_graph(10) + P3 = nx.path_graph(3) + P10 = nx.path_graph(10) + # null graph + G = nx.strong_product(null, null) + assert nx.is_isomorphic(G, null) + # null_graph X anything = null_graph and v.v. + G = nx.strong_product(null, empty10) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(null, K3) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(null, K10) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(null, P3) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(null, P10) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(empty10, null) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(K3, null) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(K10, null) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(P3, null) + assert nx.is_isomorphic(G, null) + G = nx.strong_product(P10, null) + assert nx.is_isomorphic(G, null) + + +def test_strong_product_size(): + K5 = nx.complete_graph(5) + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.strong_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.strong_product(K3, K5) + assert nx.number_of_nodes(G) == 3 * 5 + + +def test_strong_product_combinations(): + P5 = nx.path_graph(5) + K3 = nx.complete_graph(3) + G = nx.strong_product(P5, K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.strong_product(nx.MultiGraph(P5), K3) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.strong_product(P5, nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + G = nx.strong_product(nx.MultiGraph(P5), nx.MultiGraph(K3)) + assert nx.number_of_nodes(G) == 5 * 3 + + # No classic easily found classic results for strong product + + +def test_strong_product_random(): + G = nx.erdos_renyi_graph(10, 2 / 10.0) + H = nx.erdos_renyi_graph(10, 2 / 10.0) + GH = nx.strong_product(G, H) + + for u_G, u_H in GH.nodes(): + for v_G, v_H in GH.nodes(): + if ( + (u_G == v_G and H.has_edge(u_H, v_H)) + or (u_H == v_H and G.has_edge(u_G, v_G)) + or (G.has_edge(u_G, v_G) and H.has_edge(u_H, v_H)) + ): + assert GH.has_edge((u_G, u_H), (v_G, v_H)) + else: + assert not GH.has_edge((u_G, u_H), (v_G, v_H)) + + +def test_graph_power_raises(): + with pytest.raises(nx.NetworkXNotImplemented): + nx.power(nx.MultiDiGraph(), 2) + + +def test_graph_power(): + # wikipedia example for graph power + G = nx.cycle_graph(7) + G.add_edge(6, 7) + G.add_edge(7, 8) + G.add_edge(8, 9) + G.add_edge(9, 2) + H = nx.power(G, 2) + + assert edges_equal( + list(H.edges()), + [ + (0, 1), + (0, 2), + (0, 5), + (0, 6), + (0, 7), + (1, 9), + (1, 2), + (1, 3), + (1, 6), + (2, 3), + (2, 4), + (2, 8), + (2, 9), + (3, 4), + (3, 5), + (3, 9), + (4, 5), + (4, 6), + (5, 6), + (5, 7), + (6, 7), + (6, 8), + (7, 8), + (7, 9), + (8, 9), + ], + ) + + +def test_graph_power_negative(): + with pytest.raises(ValueError): + nx.power(nx.Graph(), -1) + + +def test_rooted_product_raises(): + with pytest.raises(nx.NodeNotFound): + nx.rooted_product(nx.Graph(), nx.path_graph(2), 10) + + +def test_rooted_product(): + G = nx.cycle_graph(5) + H = nx.Graph() + H.add_edges_from([("a", "b"), ("b", "c"), ("b", "d")]) + R = nx.rooted_product(G, H, "a") + assert len(R) == len(G) * len(H) + assert R.size() == G.size() + len(G) * H.size() + + +def test_corona_product(): + G = nx.cycle_graph(3) + H = nx.path_graph(2) + C = nx.corona_product(G, H) + assert len(C) == (len(G) * len(H)) + len(G) + assert C.size() == G.size() + len(G) * H.size() + len(G) * len(H) + + +def test_modular_product(): + G = nx.path_graph(3) + H = nx.path_graph(4) + M = nx.modular_product(G, H) + assert len(M) == len(G) * len(H) + + assert edges_equal( + list(M.edges()), + [ + ((0, 0), (1, 1)), + ((0, 0), (2, 2)), + ((0, 0), (2, 3)), + ((0, 1), (1, 0)), + ((0, 1), (1, 2)), + ((0, 1), (2, 3)), + ((0, 2), (1, 1)), + ((0, 2), (1, 3)), + ((0, 2), (2, 0)), + ((0, 3), (1, 2)), + ((0, 3), (2, 0)), + ((0, 3), (2, 1)), + ((1, 0), (2, 1)), + ((1, 1), (2, 0)), + ((1, 1), (2, 2)), + ((1, 2), (2, 1)), + ((1, 2), (2, 3)), + ((1, 3), (2, 2)), + ], + ) + + +def test_modular_product_raises(): + G = nx.Graph([(0, 1), (1, 2), (2, 0)]) + H = nx.Graph([(0, 1), (1, 2), (2, 0)]) + DG = nx.DiGraph([(0, 1), (1, 2), (2, 0)]) + DH = nx.DiGraph([(0, 1), (1, 2), (2, 0)]) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(G, DH) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(DG, H) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(DG, DH) + + MG = nx.MultiGraph([(0, 1), (1, 2), (2, 0), (0, 1)]) + MH = nx.MultiGraph([(0, 1), (1, 2), (2, 0), (0, 1)]) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(G, MH) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(MG, H) + with pytest.raises(nx.NetworkXNotImplemented): + nx.modular_product(MG, MH) + with pytest.raises(nx.NetworkXNotImplemented): + # check multigraph with no multiedges + nx.modular_product(nx.MultiGraph(G), H) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py new file mode 100644 index 0000000000000000000000000000000000000000..d68e55cd9c9fa37459b497c32a7a095576c306c3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/tests/test_unary.py @@ -0,0 +1,55 @@ +import pytest + +import networkx as nx + + +def test_complement(): + null = nx.null_graph() + empty1 = nx.empty_graph(1) + empty10 = nx.empty_graph(10) + K3 = nx.complete_graph(3) + K5 = nx.complete_graph(5) + K10 = nx.complete_graph(10) + P2 = nx.path_graph(2) + P3 = nx.path_graph(3) + P5 = nx.path_graph(5) + P10 = nx.path_graph(10) + # complement of the complete graph is empty + + G = nx.complement(K3) + assert nx.is_isomorphic(G, nx.empty_graph(3)) + G = nx.complement(K5) + assert nx.is_isomorphic(G, nx.empty_graph(5)) + # for any G, G=complement(complement(G)) + P3cc = nx.complement(nx.complement(P3)) + assert nx.is_isomorphic(P3, P3cc) + nullcc = nx.complement(nx.complement(null)) + assert nx.is_isomorphic(null, nullcc) + b = nx.bull_graph() + bcc = nx.complement(nx.complement(b)) + assert nx.is_isomorphic(b, bcc) + + +def test_complement_2(): + G1 = nx.DiGraph() + G1.add_edge("A", "B") + G1.add_edge("A", "C") + G1.add_edge("A", "D") + G1C = nx.complement(G1) + assert sorted(G1C.edges()) == [ + ("B", "A"), + ("B", "C"), + ("B", "D"), + ("C", "A"), + ("C", "B"), + ("C", "D"), + ("D", "A"), + ("D", "B"), + ("D", "C"), + ] + + +def test_reverse1(): + # Other tests for reverse are done by the DiGraph and MultiDigraph. + G1 = nx.Graph() + pytest.raises(nx.NetworkXError, nx.reverse, G1) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/unary.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/unary.py new file mode 100644 index 0000000000000000000000000000000000000000..79e44d1cc04cff72c5c87d1852544514a6f53246 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/operators/unary.py @@ -0,0 +1,77 @@ +"""Unary operations on graphs""" + +import networkx as nx + +__all__ = ["complement", "reverse"] + + +@nx._dispatchable(returns_graph=True) +def complement(G): + """Returns the graph complement of G. + + Parameters + ---------- + G : graph + A NetworkX graph + + Returns + ------- + GC : A new graph. + + Notes + ----- + Note that `complement` does not create self-loops and also + does not produce parallel edges for MultiGraphs. + + Graph, node, and edge data are not propagated to the new graph. + + Examples + -------- + >>> G = nx.Graph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)]) + >>> G_complement = nx.complement(G) + >>> G_complement.edges() # This shows the edges of the complemented graph + EdgeView([(1, 4), (1, 5), (2, 4), (2, 5), (4, 5)]) + + """ + R = G.__class__() + R.add_nodes_from(G) + R.add_edges_from( + ((n, n2) for n, nbrs in G.adjacency() for n2 in G if n2 not in nbrs if n != n2) + ) + return R + + +@nx._dispatchable(returns_graph=True) +def reverse(G, copy=True): + """Returns the reverse directed graph of G. + + Parameters + ---------- + G : directed graph + A NetworkX directed graph + copy : bool + If True, then a new graph is returned. If False, then the graph is + reversed in place. + + Returns + ------- + H : directed graph + The reversed G. + + Raises + ------ + NetworkXError + If graph is undirected. + + Examples + -------- + >>> G = nx.DiGraph([(1, 2), (1, 3), (2, 3), (3, 4), (3, 5)]) + >>> G_reversed = nx.reverse(G) + >>> G_reversed.edges() + OutEdgeView([(2, 1), (3, 1), (3, 2), (4, 3), (5, 3)]) + + """ + if not G.is_directed(): + raise nx.NetworkXError("Cannot reverse an undirected graph.") + else: + return G.reverse(copy=copy) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a12a6f03689b59d39be9b69584aeb46049ab8ca Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/astar.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/astar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b38c82925af634773c002237eede955d60b00734 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/astar.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/dense.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/dense.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7da539898c35c939f37e4e434490286b00922bcc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/dense.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/generic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/generic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5829924f962f4eec5186e9366f7de5e7f3770bd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/generic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/unweighted.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/unweighted.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d93a0ab5eded14075eeb79dc3f3afc71ddb9925d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/unweighted.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/weighted.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/weighted.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b549179a1e81b28c66a0c4e82f14c9d199005ad Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/__pycache__/weighted.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21c5f7e888dcf0731016e5105e219addb26b5892 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_astar.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_astar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f22e3586571adfb06edfe75bc42e5b61cc5c5963 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_astar.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab7c99c52a0d21d7b0eea07dc8ebea7e1d4556f7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense_numpy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense_numpy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6af3657f7f37695e50315f0f148a35c870de7464 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_dense_numpy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_generic.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_generic.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cfed81e0bea506df0820b60a4b63d0c10462f83 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_generic.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_unweighted.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_unweighted.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05143fe65162e7a9f705cbca3efae6796a8856e1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_unweighted.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_weighted.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_weighted.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8492c787277f29c7ade6e29f169c3414b6f12a5b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/__pycache__/test_weighted.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py new file mode 100644 index 0000000000000000000000000000000000000000..3abf2114528cd7708b7a36baf13cb3172eb342fa --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_astar.py @@ -0,0 +1,254 @@ +import pytest + +import networkx as nx +from networkx.utils import pairwise + + +class TestAStar: + @classmethod + def setup_class(cls): + edges = [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + cls.XG = nx.DiGraph() + cls.XG.add_weighted_edges_from(edges) + + def test_multiple_optimal_paths(self): + """Tests that A* algorithm finds any of multiple optimal paths""" + heuristic_values = {"a": 1.35, "b": 1.18, "c": 0.67, "d": 0} + + def h(u, v): + return heuristic_values[u] + + graph = nx.Graph() + points = ["a", "b", "c", "d"] + edges = [("a", "b", 0.18), ("a", "c", 0.68), ("b", "c", 0.50), ("c", "d", 0.67)] + + graph.add_nodes_from(points) + graph.add_weighted_edges_from(edges) + + path1 = ["a", "c", "d"] + path2 = ["a", "b", "c", "d"] + assert nx.astar_path(graph, "a", "d", h) in (path1, path2) + + def test_astar_directed(self): + assert nx.astar_path(self.XG, "s", "v") == ["s", "x", "u", "v"] + assert nx.astar_path_length(self.XG, "s", "v") == 9 + + def test_astar_directed_weight_function(self): + def w1(u, v, d): + return d["weight"] + + assert nx.astar_path(self.XG, "x", "u", weight=w1) == ["x", "u"] + assert nx.astar_path_length(self.XG, "x", "u", weight=w1) == 3 + assert nx.astar_path(self.XG, "s", "v", weight=w1) == ["s", "x", "u", "v"] + assert nx.astar_path_length(self.XG, "s", "v", weight=w1) == 9 + + def w2(u, v, d): + return None if (u, v) == ("x", "u") else d["weight"] + + assert nx.astar_path(self.XG, "x", "u", weight=w2) == ["x", "y", "s", "u"] + assert nx.astar_path_length(self.XG, "x", "u", weight=w2) == 19 + assert nx.astar_path(self.XG, "s", "v", weight=w2) == ["s", "x", "v"] + assert nx.astar_path_length(self.XG, "s", "v", weight=w2) == 10 + + def w3(u, v, d): + return d["weight"] + 10 + + assert nx.astar_path(self.XG, "x", "u", weight=w3) == ["x", "u"] + assert nx.astar_path_length(self.XG, "x", "u", weight=w3) == 13 + assert nx.astar_path(self.XG, "s", "v", weight=w3) == ["s", "x", "v"] + assert nx.astar_path_length(self.XG, "s", "v", weight=w3) == 30 + + def test_astar_multigraph(self): + G = nx.MultiDiGraph(self.XG) + G.add_weighted_edges_from((u, v, 1000) for (u, v) in list(G.edges())) + assert nx.astar_path(G, "s", "v") == ["s", "x", "u", "v"] + assert nx.astar_path_length(G, "s", "v") == 9 + + def test_astar_undirected(self): + GG = self.XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG["u"]["x"]["weight"] = 2 + GG["y"]["v"]["weight"] = 2 + assert nx.astar_path(GG, "s", "v") == ["s", "x", "u", "v"] + assert nx.astar_path_length(GG, "s", "v") == 8 + + def test_astar_directed2(self): + XG2 = nx.DiGraph() + edges = [ + (1, 4, 1), + (4, 5, 1), + (5, 6, 1), + (6, 3, 1), + (1, 3, 50), + (1, 2, 100), + (2, 3, 100), + ] + XG2.add_weighted_edges_from(edges) + assert nx.astar_path(XG2, 1, 3) == [1, 4, 5, 6, 3] + + def test_astar_undirected2(self): + XG3 = nx.Graph() + edges = [(0, 1, 2), (1, 2, 12), (2, 3, 1), (3, 4, 5), (4, 5, 1), (5, 0, 10)] + XG3.add_weighted_edges_from(edges) + assert nx.astar_path(XG3, 0, 3) == [0, 1, 2, 3] + assert nx.astar_path_length(XG3, 0, 3) == 15 + + def test_astar_undirected3(self): + XG4 = nx.Graph() + edges = [ + (0, 1, 2), + (1, 2, 2), + (2, 3, 1), + (3, 4, 1), + (4, 5, 1), + (5, 6, 1), + (6, 7, 1), + (7, 0, 1), + ] + XG4.add_weighted_edges_from(edges) + assert nx.astar_path(XG4, 0, 2) == [0, 1, 2] + assert nx.astar_path_length(XG4, 0, 2) == 4 + + """ Tests that A* finds correct path when multiple paths exist + and the best one is not expanded first (GH issue #3464) + """ + + def test_astar_directed3(self): + heuristic_values = {"n5": 36, "n2": 4, "n1": 0, "n0": 0} + + def h(u, v): + return heuristic_values[u] + + edges = [("n5", "n1", 11), ("n5", "n2", 9), ("n2", "n1", 1), ("n1", "n0", 32)] + graph = nx.DiGraph() + graph.add_weighted_edges_from(edges) + answer = ["n5", "n2", "n1", "n0"] + assert nx.astar_path(graph, "n5", "n0", h) == answer + + """ Tests that parent is not wrongly overridden when a node + is re-explored multiple times. + """ + + def test_astar_directed4(self): + edges = [ + ("a", "b", 1), + ("a", "c", 1), + ("b", "d", 2), + ("c", "d", 1), + ("d", "e", 1), + ] + graph = nx.DiGraph() + graph.add_weighted_edges_from(edges) + assert nx.astar_path(graph, "a", "e") == ["a", "c", "d", "e"] + + # >>> MXG4=NX.MultiGraph(XG4) + # >>> MXG4.add_edge(0,1,3) + # >>> NX.dijkstra_path(MXG4,0,2) + # [0, 1, 2] + + def test_astar_w1(self): + G = nx.DiGraph() + G.add_edges_from( + [ + ("s", "u"), + ("s", "x"), + ("u", "v"), + ("u", "x"), + ("v", "y"), + ("x", "u"), + ("x", "w"), + ("w", "v"), + ("x", "y"), + ("y", "s"), + ("y", "v"), + ] + ) + assert nx.astar_path(G, "s", "v") == ["s", "u", "v"] + assert nx.astar_path_length(G, "s", "v") == 2 + + def test_astar_nopath(self): + with pytest.raises(nx.NodeNotFound): + nx.astar_path(self.XG, "s", "moon") + + def test_astar_cutoff(self): + with pytest.raises(nx.NetworkXNoPath): + # optimal path_length in XG is 9 + nx.astar_path(self.XG, "s", "v", cutoff=8.0) + with pytest.raises(nx.NetworkXNoPath): + nx.astar_path_length(self.XG, "s", "v", cutoff=8.0) + + def test_astar_admissible_heuristic_with_cutoff(self): + heuristic_values = {"s": 36, "y": 4, "x": 0, "u": 0, "v": 0} + + def h(u, v): + return heuristic_values[u] + + assert nx.astar_path_length(self.XG, "s", "v") == 9 + assert nx.astar_path_length(self.XG, "s", "v", heuristic=h) == 9 + assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=12) == 9 + assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=9) == 9 + with pytest.raises(nx.NetworkXNoPath): + nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=8) + + def test_astar_inadmissible_heuristic_with_cutoff(self): + heuristic_values = {"s": 36, "y": 14, "x": 10, "u": 10, "v": 0} + + def h(u, v): + return heuristic_values[u] + + # optimal path_length in XG is 9. This heuristic gives over-estimate. + assert nx.astar_path_length(self.XG, "s", "v", heuristic=h) == 10 + assert nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=15) == 10 + with pytest.raises(nx.NetworkXNoPath): + nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=9) + with pytest.raises(nx.NetworkXNoPath): + nx.astar_path_length(self.XG, "s", "v", heuristic=h, cutoff=12) + + def test_astar_cutoff2(self): + assert nx.astar_path(self.XG, "s", "v", cutoff=10.0) == ["s", "x", "u", "v"] + assert nx.astar_path_length(self.XG, "s", "v") == 9 + + def test_cycle(self): + C = nx.cycle_graph(7) + assert nx.astar_path(C, 0, 3) == [0, 1, 2, 3] + assert nx.dijkstra_path(C, 0, 4) == [0, 6, 5, 4] + + def test_unorderable_nodes(self): + """Tests that A* accommodates nodes that are not orderable. + + For more information, see issue #554. + + """ + # Create the cycle graph on four nodes, with nodes represented + # as (unorderable) Python objects. + nodes = [object() for n in range(4)] + G = nx.Graph() + G.add_edges_from(pairwise(nodes, cyclic=True)) + path = nx.astar_path(G, nodes[0], nodes[2]) + assert len(path) == 3 + + def test_astar_NetworkXNoPath(self): + """Tests that exception is raised when there exists no + path between source and target""" + G = nx.gnp_random_graph(10, 0.2, seed=10) + with pytest.raises(nx.NetworkXNoPath): + nx.astar_path(G, 4, 9) + + def test_astar_NodeNotFound(self): + """Tests that exception is raised when either + source or target is not in graph""" + G = nx.gnp_random_graph(10, 0.2, seed=10) + with pytest.raises(nx.NodeNotFound): + nx.astar_path_length(G, 11, 9) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py new file mode 100644 index 0000000000000000000000000000000000000000..6923bfef856c83bd3e65573b97fe96ff16cdbc71 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense.py @@ -0,0 +1,212 @@ +import pytest + +import networkx as nx + + +class TestFloyd: + @classmethod + def setup_class(cls): + pass + + def test_floyd_warshall_predecessor_and_distance(self): + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG) + assert dist["s"]["v"] == 9 + assert path["s"]["v"] == "u" + assert dist == { + "y": {"y": 0, "x": 12, "s": 7, "u": 15, "v": 6}, + "x": {"y": 2, "x": 0, "s": 9, "u": 3, "v": 4}, + "s": {"y": 7, "x": 5, "s": 0, "u": 8, "v": 9}, + "u": {"y": 2, "x": 2, "s": 9, "u": 0, "v": 1}, + "v": {"y": 1, "x": 13, "s": 8, "u": 16, "v": 0}, + } + + GG = XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG["u"]["x"]["weight"] = 2 + path, dist = nx.floyd_warshall_predecessor_and_distance(GG) + assert dist["s"]["v"] == 8 + # skip this test, could be alternate path s-u-v + # assert_equal(path['s']['v'],'y') + + G = nx.DiGraph() # no weights + G.add_edges_from( + [ + ("s", "u"), + ("s", "x"), + ("u", "v"), + ("u", "x"), + ("v", "y"), + ("x", "u"), + ("x", "v"), + ("x", "y"), + ("y", "s"), + ("y", "v"), + ] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(G) + assert dist["s"]["v"] == 2 + # skip this test, could be alternate path s-u-v + # assert_equal(path['s']['v'],'x') + + # alternate interface + dist = nx.floyd_warshall(G) + assert dist["s"]["v"] == 2 + + # floyd_warshall_predecessor_and_distance returns + # dicts-of-defautdicts + # make sure we don't get empty dictionary + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [("v", "x", 5.0), ("y", "x", 5.0), ("v", "y", 6.0), ("x", "u", 2.0)] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG) + inf = float("inf") + assert dist == { + "v": {"v": 0, "x": 5.0, "y": 6.0, "u": 7.0}, + "x": {"x": 0, "u": 2.0, "v": inf, "y": inf}, + "y": {"y": 0, "x": 5.0, "v": inf, "u": 7.0}, + "u": {"u": 0, "v": inf, "x": inf, "y": inf}, + } + assert path == { + "v": {"x": "v", "y": "v", "u": "x"}, + "x": {"u": "x"}, + "y": {"x": "y", "u": "x"}, + } + + def test_reconstruct_path(self): + with pytest.raises(KeyError): + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + predecessors, _ = nx.floyd_warshall_predecessor_and_distance(XG) + + path = nx.reconstruct_path("s", "v", predecessors) + assert path == ["s", "x", "u", "v"] + + path = nx.reconstruct_path("s", "s", predecessors) + assert path == [] + + # this part raises the keyError + nx.reconstruct_path("1", "2", predecessors) + + def test_cycle(self): + path, dist = nx.floyd_warshall_predecessor_and_distance(nx.cycle_graph(7)) + assert dist[0][3] == 3 + assert path[0][3] == 2 + assert dist[0][4] == 3 + + def test_weighted(self): + XG3 = nx.Graph() + XG3.add_weighted_edges_from( + [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG3) + assert dist[0][3] == 15 + assert path[0][3] == 2 + + def test_weighted2(self): + XG4 = nx.Graph() + XG4.add_weighted_edges_from( + [ + [0, 1, 2], + [1, 2, 2], + [2, 3, 1], + [3, 4, 1], + [4, 5, 1], + [5, 6, 1], + [6, 7, 1], + [7, 0, 1], + ] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG4) + assert dist[0][2] == 4 + assert path[0][2] == 1 + + def test_weight_parameter(self): + XG4 = nx.Graph() + XG4.add_edges_from( + [ + (0, 1, {"heavy": 2}), + (1, 2, {"heavy": 2}), + (2, 3, {"heavy": 1}), + (3, 4, {"heavy": 1}), + (4, 5, {"heavy": 1}), + (5, 6, {"heavy": 1}), + (6, 7, {"heavy": 1}), + (7, 0, {"heavy": 1}), + ] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG4, weight="heavy") + assert dist[0][2] == 4 + assert path[0][2] == 1 + + def test_zero_distance(self): + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + path, dist = nx.floyd_warshall_predecessor_and_distance(XG) + + for u in XG: + assert dist[u][u] == 0 + + GG = XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG["u"]["x"]["weight"] = 2 + path, dist = nx.floyd_warshall_predecessor_and_distance(GG) + + for u in GG: + dist[u][u] = 0 + + def test_zero_weight(self): + G = nx.DiGraph() + edges = [(1, 2, -2), (2, 3, -4), (1, 5, 1), (5, 4, 0), (4, 3, -5), (2, 5, -7)] + G.add_weighted_edges_from(edges) + dist = nx.floyd_warshall(G) + assert dist[1][3] == -14 + + G = nx.MultiDiGraph() + edges.append((2, 5, -7)) + G.add_weighted_edges_from(edges) + dist = nx.floyd_warshall(G) + assert dist[1][3] == -14 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py new file mode 100644 index 0000000000000000000000000000000000000000..6eb95a5280bd74d8b88cfc3049f020e7ee187a51 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py @@ -0,0 +1,88 @@ +import pytest + +import networkx as nx + +np = pytest.importorskip("numpy") + + +def test_cycle_numpy(): + dist = nx.floyd_warshall_numpy(nx.cycle_graph(7)) + assert dist[0, 3] == 3 + assert dist[0, 4] == 3 + + +def test_weighted_numpy_three_edges(): + XG3 = nx.Graph() + XG3.add_weighted_edges_from( + [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]] + ) + dist = nx.floyd_warshall_numpy(XG3) + assert dist[0, 3] == 15 + + +def test_weighted_numpy_two_edges(): + XG4 = nx.Graph() + XG4.add_weighted_edges_from( + [ + [0, 1, 2], + [1, 2, 2], + [2, 3, 1], + [3, 4, 1], + [4, 5, 1], + [5, 6, 1], + [6, 7, 1], + [7, 0, 1], + ] + ) + dist = nx.floyd_warshall_numpy(XG4) + assert dist[0, 2] == 4 + + +def test_weight_parameter_numpy(): + XG4 = nx.Graph() + XG4.add_edges_from( + [ + (0, 1, {"heavy": 2}), + (1, 2, {"heavy": 2}), + (2, 3, {"heavy": 1}), + (3, 4, {"heavy": 1}), + (4, 5, {"heavy": 1}), + (5, 6, {"heavy": 1}), + (6, 7, {"heavy": 1}), + (7, 0, {"heavy": 1}), + ] + ) + dist = nx.floyd_warshall_numpy(XG4, weight="heavy") + assert dist[0, 2] == 4 + + +def test_directed_cycle_numpy(): + G = nx.DiGraph() + nx.add_cycle(G, [0, 1, 2, 3]) + pred, dist = nx.floyd_warshall_predecessor_and_distance(G) + D = nx.utils.dict_to_numpy_array(dist) + np.testing.assert_equal(nx.floyd_warshall_numpy(G), D) + + +def test_zero_weight(): + G = nx.DiGraph() + edges = [(1, 2, -2), (2, 3, -4), (1, 5, 1), (5, 4, 0), (4, 3, -5), (2, 5, -7)] + G.add_weighted_edges_from(edges) + dist = nx.floyd_warshall_numpy(G) + assert int(np.min(dist)) == -14 + + G = nx.MultiDiGraph() + edges.append((2, 5, -7)) + G.add_weighted_edges_from(edges) + dist = nx.floyd_warshall_numpy(G) + assert int(np.min(dist)) == -14 + + +def test_nodelist(): + G = nx.path_graph(7) + dist = nx.floyd_warshall_numpy(G, nodelist=[3, 5, 4, 6, 2, 1, 0]) + assert dist[0, 3] == 3 + assert dist[0, 1] == 2 + assert dist[6, 2] == 4 + pytest.raises(nx.NetworkXError, nx.floyd_warshall_numpy, G, [1, 3]) + pytest.raises(nx.NetworkXError, nx.floyd_warshall_numpy, G, list(range(9))) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py new file mode 100644 index 0000000000000000000000000000000000000000..09f6953d4bce83d79db2f424c5645c65cb6e2234 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_generic.py @@ -0,0 +1,511 @@ +import pytest + +import networkx as nx + + +def validate_grid_path(r, c, s, t, p): + assert isinstance(p, list) + assert p[0] == s + assert p[-1] == t + s = ((s - 1) // c, (s - 1) % c) + t = ((t - 1) // c, (t - 1) % c) + assert len(p) == abs(t[0] - s[0]) + abs(t[1] - s[1]) + 1 + p = [((u - 1) // c, (u - 1) % c) for u in p] + for u in p: + assert 0 <= u[0] < r + assert 0 <= u[1] < c + for u, v in zip(p[:-1], p[1:]): + assert (abs(v[0] - u[0]), abs(v[1] - u[1])) in [(0, 1), (1, 0)] + + +class TestGenericPath: + @classmethod + def setup_class(cls): + from networkx import convert_node_labels_to_integers as cnlti + + cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted") + cls.cycle = nx.cycle_graph(7) + cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) + cls.neg_weights = nx.DiGraph() + cls.neg_weights.add_edge(0, 1, weight=1) + cls.neg_weights.add_edge(0, 2, weight=3) + cls.neg_weights.add_edge(1, 3, weight=1) + cls.neg_weights.add_edge(2, 3, weight=-2) + + def test_sentinel_trick_all_algorithms(self): + def reconstruct_path(pred, source, target): + path = [target] + while path[-1] != source: + path.append(pred[path[-1]]) + return list(reversed(path)) + + # Build the test graph inline + G = nx.Graph() + G.add_edge("A", "B", weight=1) + G.add_edge("B", "C", weight=1) + G.add_edge("C", "D", weight=1) + G.add_edge("D", "E", weight=1) + + source = "A" + targets = {"C", "D", "E"} + expected_target = "C" # A-B-C is closest + + sentinel = "_sentinel_" + G.add_node(sentinel) + for t in targets: + G.add_edge(t, sentinel, weight=0) + + # shortest_path: Dijkstra (default) + path = nx.shortest_path(G, source=source, target=sentinel, weight="weight") + assert path[-2] == expected_target + + # shortest_path: Bellman-Ford + path = nx.shortest_path( + G, source=source, target=sentinel, weight="weight", method="bellman-ford" + ) + assert path[-2] == expected_target + + # shortest_path: Unweighted (BFS) + path = nx.shortest_path(G, source=source, target=sentinel, weight=None) + assert path[-2] == expected_target + + # bidirectional_dijkstra + _, path = nx.bidirectional_dijkstra(G, source, sentinel, weight="weight") + assert path[-2] == expected_target + + # goldberg_radzik + pred, _ = nx.goldberg_radzik(G, source, weight="weight") + path = reconstruct_path(pred, source, sentinel) + assert path[-2] == expected_target + + # astar_path with zero heuristic + path = nx.astar_path( + G, source, sentinel, heuristic=lambda u, v: 0, weight="weight" + ) + assert path[-2] == expected_target + + # johnson (all-pairs shortest paths) + paths = nx.johnson(G, weight="weight") + assert paths[source][sentinel][-2] == expected_target + + # floyd_warshall_predecessor_and_distance + pred, _ = nx.floyd_warshall_predecessor_and_distance(G, weight="weight") + path = reconstruct_path(pred[source], source, sentinel) + assert path[-2] == expected_target + + def test_shortest_path(self): + assert nx.shortest_path(self.cycle, 0, 3) == [0, 1, 2, 3] + assert nx.shortest_path(self.cycle, 0, 4) == [0, 6, 5, 4] + validate_grid_path(4, 4, 1, 12, nx.shortest_path(self.grid, 1, 12)) + assert nx.shortest_path(self.directed_cycle, 0, 3) == [0, 1, 2, 3] + # now with weights + assert nx.shortest_path(self.cycle, 0, 3, weight="weight") == [0, 1, 2, 3] + assert nx.shortest_path(self.cycle, 0, 4, weight="weight") == [0, 6, 5, 4] + validate_grid_path( + 4, 4, 1, 12, nx.shortest_path(self.grid, 1, 12, weight="weight") + ) + assert nx.shortest_path(self.directed_cycle, 0, 3, weight="weight") == [ + 0, + 1, + 2, + 3, + ] + # weights and method specified + assert nx.shortest_path( + self.directed_cycle, 0, 3, weight="weight", method="dijkstra" + ) == [0, 1, 2, 3] + assert nx.shortest_path( + self.directed_cycle, 0, 3, weight="weight", method="bellman-ford" + ) == [0, 1, 2, 3] + # when Dijkstra's will probably (depending on precise implementation) + # incorrectly return [0, 1, 3] instead + assert nx.shortest_path( + self.neg_weights, 0, 3, weight="weight", method="bellman-ford" + ) == [0, 2, 3] + # confirm bad method rejection + pytest.raises(ValueError, nx.shortest_path, self.cycle, method="SPAM") + # confirm absent source rejection + pytest.raises(nx.NodeNotFound, nx.shortest_path, self.cycle, 8) + + def test_shortest_path_target(self): + answer = {0: [0, 1], 1: [1], 2: [2, 1]} + sp = nx.shortest_path(nx.path_graph(3), target=1) + assert sp == answer + # with weights + sp = nx.shortest_path(nx.path_graph(3), target=1, weight="weight") + assert sp == answer + # weights and method specified + sp = nx.shortest_path( + nx.path_graph(3), target=1, weight="weight", method="dijkstra" + ) + assert sp == answer + sp = nx.shortest_path( + nx.path_graph(3), target=1, weight="weight", method="bellman-ford" + ) + assert sp == answer + + def test_shortest_path_length(self): + assert nx.shortest_path_length(self.cycle, 0, 3) == 3 + assert nx.shortest_path_length(self.grid, 1, 12) == 5 + assert nx.shortest_path_length(self.directed_cycle, 0, 4) == 4 + # now with weights + assert nx.shortest_path_length(self.cycle, 0, 3, weight="weight") == 3 + assert nx.shortest_path_length(self.grid, 1, 12, weight="weight") == 5 + assert nx.shortest_path_length(self.directed_cycle, 0, 4, weight="weight") == 4 + # weights and method specified + assert ( + nx.shortest_path_length( + self.cycle, 0, 3, weight="weight", method="dijkstra" + ) + == 3 + ) + assert ( + nx.shortest_path_length( + self.cycle, 0, 3, weight="weight", method="bellman-ford" + ) + == 3 + ) + # confirm bad method rejection + pytest.raises(ValueError, nx.shortest_path_length, self.cycle, method="SPAM") + # confirm absent source rejection + pytest.raises(nx.NodeNotFound, nx.shortest_path_length, self.cycle, 8) + + def test_shortest_path_length_target(self): + answer = {0: 1, 1: 0, 2: 1} + sp = nx.shortest_path_length(nx.path_graph(3), target=1) + assert sp == answer + # with weights + sp = nx.shortest_path_length(nx.path_graph(3), target=1, weight="weight") + assert sp == answer + # weights and method specified + sp = nx.shortest_path_length( + nx.path_graph(3), target=1, weight="weight", method="dijkstra" + ) + assert sp == answer + sp = nx.shortest_path_length( + nx.path_graph(3), target=1, weight="weight", method="bellman-ford" + ) + assert sp == answer + + def test_single_source_shortest_path(self): + p = nx.shortest_path(self.cycle, 0) + assert p[3] == [0, 1, 2, 3] + assert p == nx.single_source_shortest_path(self.cycle, 0) + p = nx.shortest_path(self.grid, 1) + validate_grid_path(4, 4, 1, 12, p[12]) + # now with weights + p = nx.shortest_path(self.cycle, 0, weight="weight") + assert p[3] == [0, 1, 2, 3] + assert p == nx.single_source_dijkstra_path(self.cycle, 0) + p = nx.shortest_path(self.grid, 1, weight="weight") + validate_grid_path(4, 4, 1, 12, p[12]) + # weights and method specified + p = nx.shortest_path(self.cycle, 0, method="dijkstra", weight="weight") + assert p[3] == [0, 1, 2, 3] + assert p == nx.single_source_shortest_path(self.cycle, 0) + p = nx.shortest_path(self.cycle, 0, method="bellman-ford", weight="weight") + assert p[3] == [0, 1, 2, 3] + assert p == nx.single_source_shortest_path(self.cycle, 0) + + def test_single_source_shortest_path_length(self): + ans = nx.shortest_path_length(self.cycle, 0) + assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == nx.single_source_shortest_path_length(self.cycle, 0) + ans = nx.shortest_path_length(self.grid, 1) + assert ans[16] == 6 + # now with weights + ans = nx.shortest_path_length(self.cycle, 0, weight="weight") + assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == nx.single_source_dijkstra_path_length(self.cycle, 0) + ans = nx.shortest_path_length(self.grid, 1, weight="weight") + assert ans[16] == 6 + # weights and method specified + ans = dict( + nx.shortest_path_length(self.cycle, 0, weight="weight", method="dijkstra") + ) + assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == nx.single_source_dijkstra_path_length(self.cycle, 0) + ans = dict( + nx.shortest_path_length( + self.cycle, 0, weight="weight", method="bellman-ford" + ) + ) + assert ans == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == nx.single_source_bellman_ford_path_length(self.cycle, 0) + + def test_single_source_all_shortest_paths(self): + cycle_ans = {0: [[0]], 1: [[0, 1]], 2: [[0, 1, 2], [0, 3, 2]], 3: [[0, 3]]} + ans = dict(nx.single_source_all_shortest_paths(nx.cycle_graph(4), 0)) + assert sorted(ans[2]) == cycle_ans[2] + ans = dict(nx.single_source_all_shortest_paths(self.grid, 1)) + grid_ans = [ + [1, 2, 3, 7, 11], + [1, 2, 6, 7, 11], + [1, 2, 6, 10, 11], + [1, 5, 6, 7, 11], + [1, 5, 6, 10, 11], + [1, 5, 9, 10, 11], + ] + assert sorted(ans[11]) == grid_ans + ans = dict( + nx.single_source_all_shortest_paths(nx.cycle_graph(4), 0, weight="weight") + ) + assert sorted(ans[2]) == cycle_ans[2] + ans = dict( + nx.single_source_all_shortest_paths( + nx.cycle_graph(4), 0, method="bellman-ford", weight="weight" + ) + ) + assert sorted(ans[2]) == cycle_ans[2] + ans = dict(nx.single_source_all_shortest_paths(self.grid, 1, weight="weight")) + assert sorted(ans[11]) == grid_ans + ans = dict( + nx.single_source_all_shortest_paths( + self.grid, 1, method="bellman-ford", weight="weight" + ) + ) + assert sorted(ans[11]) == grid_ans + G = nx.cycle_graph(4) + G.add_node(4) + ans = dict(nx.single_source_all_shortest_paths(G, 0)) + assert sorted(ans[2]) == [[0, 1, 2], [0, 3, 2]] + ans = dict(nx.single_source_all_shortest_paths(G, 4)) + assert sorted(ans[4]) == [[4]] + + def test_all_pairs_shortest_path(self): + # shortest_path w/o source and target returns a generator instead of + # a dict beginning in version 3.5. Only the first call needed changed here. + p = dict(nx.shortest_path(self.cycle)) + assert p[0][3] == [0, 1, 2, 3] + assert p == dict(nx.all_pairs_shortest_path(self.cycle)) + p = dict(nx.shortest_path(self.grid)) + validate_grid_path(4, 4, 1, 12, p[1][12]) + # now with weights + p = dict(nx.shortest_path(self.cycle, weight="weight")) + assert p[0][3] == [0, 1, 2, 3] + assert p == dict(nx.all_pairs_dijkstra_path(self.cycle)) + p = dict(nx.shortest_path(self.grid, weight="weight")) + validate_grid_path(4, 4, 1, 12, p[1][12]) + # weights and method specified + p = dict(nx.shortest_path(self.cycle, weight="weight", method="dijkstra")) + assert p[0][3] == [0, 1, 2, 3] + assert p == dict(nx.all_pairs_dijkstra_path(self.cycle)) + p = dict(nx.shortest_path(self.cycle, weight="weight", method="bellman-ford")) + assert p[0][3] == [0, 1, 2, 3] + assert p == dict(nx.all_pairs_bellman_ford_path(self.cycle)) + + def test_all_pairs_shortest_path_length(self): + ans = dict(nx.shortest_path_length(self.cycle)) + assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == dict(nx.all_pairs_shortest_path_length(self.cycle)) + ans = dict(nx.shortest_path_length(self.grid)) + assert ans[1][16] == 6 + # now with weights + ans = dict(nx.shortest_path_length(self.cycle, weight="weight")) + assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == dict(nx.all_pairs_dijkstra_path_length(self.cycle)) + ans = dict(nx.shortest_path_length(self.grid, weight="weight")) + assert ans[1][16] == 6 + # weights and method specified + ans = dict( + nx.shortest_path_length(self.cycle, weight="weight", method="dijkstra") + ) + assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == dict(nx.all_pairs_dijkstra_path_length(self.cycle)) + ans = dict( + nx.shortest_path_length(self.cycle, weight="weight", method="bellman-ford") + ) + assert ans[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert ans == dict(nx.all_pairs_bellman_ford_path_length(self.cycle)) + + def test_all_pairs_all_shortest_paths(self): + ans = dict(nx.all_pairs_all_shortest_paths(nx.cycle_graph(4))) + assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]] + ans = dict(nx.all_pairs_all_shortest_paths(nx.cycle_graph(4)), weight="weight") + assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]] + ans = dict( + nx.all_pairs_all_shortest_paths(nx.cycle_graph(4)), + method="bellman-ford", + weight="weight", + ) + assert sorted(ans[1][3]) == [[1, 0, 3], [1, 2, 3]] + G = nx.cycle_graph(4) + G.add_node(4) + ans = dict(nx.all_pairs_all_shortest_paths(G)) + assert sorted(ans[4][4]) == [[4]] + + def test_has_path(self): + G = nx.Graph() + nx.add_path(G, range(3)) + nx.add_path(G, range(3, 5)) + assert nx.has_path(G, 0, 2) + assert not nx.has_path(G, 0, 4) + + def test_has_path_singleton(self): + G = nx.empty_graph(1) + assert nx.has_path(G, 0, 0) + + def test_all_shortest_paths(self): + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3]) + nx.add_path(G, [0, 10, 20, 3]) + assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted(nx.all_shortest_paths(G, 0, 3)) + # with weights + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3]) + nx.add_path(G, [0, 10, 20, 3]) + assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted( + nx.all_shortest_paths(G, 0, 3, weight="weight") + ) + # weights and method specified + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3]) + nx.add_path(G, [0, 10, 20, 3]) + assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted( + nx.all_shortest_paths(G, 0, 3, weight="weight", method="dijkstra") + ) + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3]) + nx.add_path(G, [0, 10, 20, 3]) + assert [[0, 1, 2, 3], [0, 10, 20, 3]] == sorted( + nx.all_shortest_paths(G, 0, 3, weight="weight", method="bellman-ford") + ) + + def test_all_shortest_paths_raise(self): + with pytest.raises(nx.NetworkXNoPath): + G = nx.path_graph(4) + G.add_node(4) + list(nx.all_shortest_paths(G, 0, 4)) + + def test_bad_method(self): + with pytest.raises(ValueError): + G = nx.path_graph(2) + list(nx.all_shortest_paths(G, 0, 1, weight="weight", method="SPAM")) + + def test_single_source_all_shortest_paths_bad_method(self): + with pytest.raises(ValueError): + G = nx.path_graph(2) + dict( + nx.single_source_all_shortest_paths( + G, 0, weight="weight", method="SPAM" + ) + ) + + def test_all_shortest_paths_zero_weight_edge(self): + g = nx.Graph() + nx.add_path(g, [0, 1, 3]) + nx.add_path(g, [0, 1, 2, 3]) + g.edges[1, 2]["weight"] = 0 + paths30d = list( + nx.all_shortest_paths(g, 3, 0, weight="weight", method="dijkstra") + ) + paths03d = list( + nx.all_shortest_paths(g, 0, 3, weight="weight", method="dijkstra") + ) + paths30b = list( + nx.all_shortest_paths(g, 3, 0, weight="weight", method="bellman-ford") + ) + paths03b = list( + nx.all_shortest_paths(g, 0, 3, weight="weight", method="bellman-ford") + ) + assert sorted(paths03d) == sorted(p[::-1] for p in paths30d) + assert sorted(paths03d) == sorted(p[::-1] for p in paths30b) + assert sorted(paths03b) == sorted(p[::-1] for p in paths30b) + + +class TestAverageShortestPathLength: + def test_cycle_graph(self): + ans = nx.average_shortest_path_length(nx.cycle_graph(7)) + assert ans == pytest.approx(2, abs=1e-7) + + def test_path_graph(self): + ans = nx.average_shortest_path_length(nx.path_graph(5)) + assert ans == pytest.approx(2, abs=1e-7) + + def test_weighted(self): + G = nx.Graph() + nx.add_cycle(G, range(7), weight=2) + ans = nx.average_shortest_path_length(G, weight="weight") + assert ans == pytest.approx(4, abs=1e-7) + G = nx.Graph() + nx.add_path(G, range(5), weight=2) + ans = nx.average_shortest_path_length(G, weight="weight") + assert ans == pytest.approx(4, abs=1e-7) + + def test_specified_methods(self): + G = nx.Graph() + nx.add_cycle(G, range(7), weight=2) + ans = nx.average_shortest_path_length(G, weight="weight", method="dijkstra") + assert ans == pytest.approx(4, abs=1e-7) + ans = nx.average_shortest_path_length(G, weight="weight", method="bellman-ford") + assert ans == pytest.approx(4, abs=1e-7) + ans = nx.average_shortest_path_length( + G, weight="weight", method="floyd-warshall" + ) + assert ans == pytest.approx(4, abs=1e-7) + + G = nx.Graph() + nx.add_path(G, range(5), weight=2) + ans = nx.average_shortest_path_length(G, weight="weight", method="dijkstra") + assert ans == pytest.approx(4, abs=1e-7) + ans = nx.average_shortest_path_length(G, weight="weight", method="bellman-ford") + assert ans == pytest.approx(4, abs=1e-7) + ans = nx.average_shortest_path_length( + G, weight="weight", method="floyd-warshall" + ) + assert ans == pytest.approx(4, abs=1e-7) + + def test_directed_not_strongly_connected(self): + G = nx.DiGraph([(0, 1)]) + with pytest.raises(nx.NetworkXError, match="Graph is not strongly connected"): + nx.average_shortest_path_length(G) + + def test_undirected_not_connected(self): + g = nx.Graph() + g.add_nodes_from(range(3)) + g.add_edge(0, 1) + pytest.raises(nx.NetworkXError, nx.average_shortest_path_length, g) + + def test_trivial_graph(self): + """Tests that the trivial graph has average path length zero, + since there is exactly one path of length zero in the trivial + graph. + + For more information, see issue #1960. + + """ + G = nx.trivial_graph() + assert nx.average_shortest_path_length(G) == 0 + + def test_null_graph(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.average_shortest_path_length(nx.null_graph()) + + def test_bad_method(self): + with pytest.raises(ValueError): + G = nx.path_graph(2) + nx.average_shortest_path_length(G, weight="weight", method="SPAM") + + +class TestAverageShortestPathLengthNumpy: + @classmethod + def setup_class(cls): + global np + import pytest + + np = pytest.importorskip("numpy") + + def test_specified_methods_numpy(self): + G = nx.Graph() + nx.add_cycle(G, range(7), weight=2) + ans = nx.average_shortest_path_length( + G, weight="weight", method="floyd-warshall-numpy" + ) + np.testing.assert_almost_equal(ans, 4) + + G = nx.Graph() + nx.add_path(G, range(5), weight=2) + ans = nx.average_shortest_path_length( + G, weight="weight", method="floyd-warshall-numpy" + ) + np.testing.assert_almost_equal(ans, 4) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py new file mode 100644 index 0000000000000000000000000000000000000000..4adbd84de7fca27e58ba2c09edf54b92a0fbdf40 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_unweighted.py @@ -0,0 +1,149 @@ +import pytest + +import networkx as nx + + +def validate_grid_path(r, c, s, t, p): + assert isinstance(p, list) + assert p[0] == s + assert p[-1] == t + s = ((s - 1) // c, (s - 1) % c) + t = ((t - 1) // c, (t - 1) % c) + assert len(p) == abs(t[0] - s[0]) + abs(t[1] - s[1]) + 1 + p = [((u - 1) // c, (u - 1) % c) for u in p] + for u in p: + assert 0 <= u[0] < r + assert 0 <= u[1] < c + for u, v in zip(p[:-1], p[1:]): + assert (abs(v[0] - u[0]), abs(v[1] - u[1])) in [(0, 1), (1, 0)] + + +class TestUnweightedPath: + @classmethod + def setup_class(cls): + from networkx import convert_node_labels_to_integers as cnlti + + cls.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted") + cls.cycle = nx.cycle_graph(7) + cls.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) + + def test_bidirectional_shortest_path(self): + assert nx.bidirectional_shortest_path(self.cycle, 0, 3) == [0, 1, 2, 3] + assert nx.bidirectional_shortest_path(self.cycle, 0, 4) == [0, 6, 5, 4] + validate_grid_path( + 4, 4, 1, 12, nx.bidirectional_shortest_path(self.grid, 1, 12) + ) + assert nx.bidirectional_shortest_path(self.directed_cycle, 0, 3) == [0, 1, 2, 3] + # test source = target + assert nx.bidirectional_shortest_path(self.cycle, 3, 3) == [3] + + @pytest.mark.parametrize( + ("src", "tgt"), + ( + (8, 3), # source not in graph + (3, 8), # target not in graph + (8, 10), # neither source nor target in graph + (8, 8), # src == tgt, neither in graph - tests order of input checks + ), + ) + def test_bidirectional_shortest_path_src_tgt_not_in_graph(self, src, tgt): + with pytest.raises( + nx.NodeNotFound, + match=f"(Source {src}|Target {tgt}) is not in G", + ): + nx.bidirectional_shortest_path(self.cycle, src, tgt) + + def test_shortest_path_length(self): + assert nx.shortest_path_length(self.cycle, 0, 3) == 3 + assert nx.shortest_path_length(self.grid, 1, 12) == 5 + assert nx.shortest_path_length(self.directed_cycle, 0, 4) == 4 + # now with weights + assert nx.shortest_path_length(self.cycle, 0, 3, weight=True) == 3 + assert nx.shortest_path_length(self.grid, 1, 12, weight=True) == 5 + assert nx.shortest_path_length(self.directed_cycle, 0, 4, weight=True) == 4 + + def test_single_source_shortest_path(self): + p = nx.single_source_shortest_path(self.directed_cycle, 3) + assert p[0] == [3, 4, 5, 6, 0] + p = nx.single_source_shortest_path(self.cycle, 0) + assert p[3] == [0, 1, 2, 3] + p = nx.single_source_shortest_path(self.cycle, 0, cutoff=0) + assert p == {0: [0]} + + def test_single_source_shortest_path_length(self): + pl = nx.single_source_shortest_path_length + lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert dict(pl(self.cycle, 0)) == lengths + lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6} + assert dict(pl(self.directed_cycle, 0)) == lengths + + def test_single_target_shortest_path(self): + p = nx.single_target_shortest_path(self.directed_cycle, 0) + assert p[3] == [3, 4, 5, 6, 0] + p = nx.single_target_shortest_path(self.cycle, 0) + assert p[3] == [3, 2, 1, 0] + p = nx.single_target_shortest_path(self.cycle, 0, cutoff=0) + assert p == {0: [0]} + # test missing targets + target = 8 + with pytest.raises(nx.NodeNotFound, match=f"Target {target} not in G"): + nx.single_target_shortest_path(self.cycle, target) + + def test_single_target_shortest_path_length(self): + pl = nx.single_target_shortest_path_length + lengths = {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert pl(self.cycle, 0) == lengths + lengths = {0: 0, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1} + assert pl(self.directed_cycle, 0) == lengths + # test missing targets + target = 8 + with pytest.raises(nx.NodeNotFound, match=f"Target {target} is not in G"): + nx.single_target_shortest_path_length(self.cycle, target) + + def test_all_pairs_shortest_path(self): + p = dict(nx.all_pairs_shortest_path(self.cycle)) + assert p[0][3] == [0, 1, 2, 3] + p = dict(nx.all_pairs_shortest_path(self.grid)) + validate_grid_path(4, 4, 1, 12, p[1][12]) + + def test_all_pairs_shortest_path_length(self): + l = dict(nx.all_pairs_shortest_path_length(self.cycle)) + assert l[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + l = dict(nx.all_pairs_shortest_path_length(self.grid)) + assert l[1][16] == 6 + + def test_predecessor_path(self): + G = nx.path_graph(4) + assert nx.predecessor(G, 0) == {0: [], 1: [0], 2: [1], 3: [2]} + assert nx.predecessor(G, 0, 3) == [2] + + def test_predecessor_cycle(self): + G = nx.cycle_graph(4) + pred = nx.predecessor(G, 0) + assert pred[0] == [] + assert pred[1] == [0] + assert pred[2] in [[1, 3], [3, 1]] + assert pred[3] == [0] + + def test_predecessor_cutoff(self): + G = nx.path_graph(4) + p = nx.predecessor(G, 0, 3) + assert 4 not in p + + def test_predecessor_target(self): + G = nx.path_graph(4) + p = nx.predecessor(G, 0, 3) + assert p == [2] + p = nx.predecessor(G, 0, 3, cutoff=2) + assert p == [] + p, s = nx.predecessor(G, 0, 3, return_seen=True) + assert p == [2] + assert s == 3 + p, s = nx.predecessor(G, 0, 3, cutoff=2, return_seen=True) + assert p == [] + assert s == -1 + + def test_predecessor_missing_source(self): + source = 8 + with pytest.raises(nx.NodeNotFound, match=f"Source {source} not in G"): + nx.predecessor(self.cycle, source) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py new file mode 100644 index 0000000000000000000000000000000000000000..4a6a98a13b0eae03a82ef594e15368eb744c8497 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/shortest_paths/tests/test_weighted.py @@ -0,0 +1,983 @@ +import pytest + +import networkx as nx +from networkx.utils import pairwise + + +def validate_path(G, s, t, soln_len, path, weight="weight"): + assert path[0] == s + assert path[-1] == t + + if callable(weight): + weight_f = weight + else: + if G.is_multigraph(): + + def weight_f(u, v, d): + return min(e.get(weight, 1) for e in d.values()) + + else: + + def weight_f(u, v, d): + return d.get(weight, 1) + + computed = sum(weight_f(u, v, G[u][v]) for u, v in pairwise(path)) + assert soln_len == computed + + +def validate_length_path(G, s, t, soln_len, length, path, weight="weight"): + assert soln_len == length + validate_path(G, s, t, length, path, weight=weight) + + +class WeightedTestBase: + """Base class for test classes that test functions for computing + shortest paths in weighted graphs. + + """ + + def setup_method(self): + """Creates some graphs for use in the unit tests.""" + cnlti = nx.convert_node_labels_to_integers + self.grid = cnlti(nx.grid_2d_graph(4, 4), first_label=1, ordering="sorted") + self.cycle = nx.cycle_graph(7) + self.directed_cycle = nx.cycle_graph(7, create_using=nx.DiGraph()) + self.XG = nx.DiGraph() + self.XG.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + self.MXG = nx.MultiDiGraph(self.XG) + self.MXG.add_edge("s", "u", weight=15) + self.XG2 = nx.DiGraph() + self.XG2.add_weighted_edges_from( + [ + [1, 4, 1], + [4, 5, 1], + [5, 6, 1], + [6, 3, 1], + [1, 3, 50], + [1, 2, 100], + [2, 3, 100], + ] + ) + + self.XG3 = nx.Graph() + self.XG3.add_weighted_edges_from( + [[0, 1, 2], [1, 2, 12], [2, 3, 1], [3, 4, 5], [4, 5, 1], [5, 0, 10]] + ) + + self.XG4 = nx.Graph() + self.XG4.add_weighted_edges_from( + [ + [0, 1, 2], + [1, 2, 2], + [2, 3, 1], + [3, 4, 1], + [4, 5, 1], + [5, 6, 1], + [6, 7, 1], + [7, 0, 1], + ] + ) + self.MXG4 = nx.MultiGraph(self.XG4) + self.MXG4.add_edge(0, 1, weight=3) + self.G = nx.DiGraph() # no weights + self.G.add_edges_from( + [ + ("s", "u"), + ("s", "x"), + ("u", "v"), + ("u", "x"), + ("v", "y"), + ("x", "u"), + ("x", "v"), + ("x", "y"), + ("y", "s"), + ("y", "v"), + ] + ) + + +class TestWeightedPath(WeightedTestBase): + def test_dijkstra(self): + (D, P) = nx.single_source_dijkstra(self.XG, "s") + validate_path(self.XG, "s", "v", 9, P["v"]) + assert D["v"] == 9 + + validate_path( + self.XG, "s", "v", 9, nx.single_source_dijkstra_path(self.XG, "s")["v"] + ) + assert nx.single_source_dijkstra_path_length(self.XG, "s")["v"] == 9 + + validate_path( + self.XG, "s", "v", 9, nx.single_source_dijkstra(self.XG, "s")[1]["v"] + ) + validate_path( + self.MXG, "s", "v", 9, nx.single_source_dijkstra_path(self.MXG, "s")["v"] + ) + + GG = self.XG.to_undirected() + # make sure we get lower weight + # to_undirected might choose either edge with weight 2 or weight 3 + GG["u"]["x"]["weight"] = 2 + (D, P) = nx.single_source_dijkstra(GG, "s") + validate_path(GG, "s", "v", 8, P["v"]) + assert D["v"] == 8 # uses lower weight of 2 on u<->x edge + validate_path(GG, "s", "v", 8, nx.dijkstra_path(GG, "s", "v")) + assert nx.dijkstra_path_length(GG, "s", "v") == 8 + + validate_path(self.XG2, 1, 3, 4, nx.dijkstra_path(self.XG2, 1, 3)) + validate_path(self.XG3, 0, 3, 15, nx.dijkstra_path(self.XG3, 0, 3)) + assert nx.dijkstra_path_length(self.XG3, 0, 3) == 15 + validate_path(self.XG4, 0, 2, 4, nx.dijkstra_path(self.XG4, 0, 2)) + assert nx.dijkstra_path_length(self.XG4, 0, 2) == 4 + validate_path(self.MXG4, 0, 2, 4, nx.dijkstra_path(self.MXG4, 0, 2)) + validate_path( + self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s", "v")[1] + ) + validate_path( + self.G, "s", "v", 2, nx.single_source_dijkstra(self.G, "s")[1]["v"] + ) + + validate_path(self.G, "s", "v", 2, nx.dijkstra_path(self.G, "s", "v")) + assert nx.dijkstra_path_length(self.G, "s", "v") == 2 + + # NetworkXError: node s not reachable from moon + pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path, self.G, "s", "moon") + pytest.raises(nx.NetworkXNoPath, nx.dijkstra_path_length, self.G, "s", "moon") + + validate_path(self.cycle, 0, 3, 3, nx.dijkstra_path(self.cycle, 0, 3)) + validate_path(self.cycle, 0, 4, 3, nx.dijkstra_path(self.cycle, 0, 4)) + + assert nx.single_source_dijkstra(self.cycle, 0, 0) == (0, [0]) + + def test_bidirectional_dijkstra(self): + validate_length_path( + self.XG, "s", "v", 9, *nx.bidirectional_dijkstra(self.XG, "s", "v") + ) + validate_length_path( + self.G, "s", "v", 2, *nx.bidirectional_dijkstra(self.G, "s", "v") + ) + validate_length_path( + self.cycle, 0, 3, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 3) + ) + validate_length_path( + self.cycle, 0, 4, 3, *nx.bidirectional_dijkstra(self.cycle, 0, 4) + ) + validate_length_path( + self.XG3, 0, 3, 15, *nx.bidirectional_dijkstra(self.XG3, 0, 3) + ) + validate_length_path( + self.XG4, 0, 2, 4, *nx.bidirectional_dijkstra(self.XG4, 0, 2) + ) + + # need more tests here + P = nx.single_source_dijkstra_path(self.XG, "s")["v"] + validate_path( + self.XG, + "s", + "v", + sum(self.XG[u][v]["weight"] for u, v in zip(P[:-1], P[1:])), + nx.dijkstra_path(self.XG, "s", "v"), + ) + + # check absent source + G = nx.path_graph(2) + pytest.raises(nx.NodeNotFound, nx.bidirectional_dijkstra, G, 3, 0) + + def test_weight_functions(self): + def heuristic(*z): + return sum(val**2 for val in z) + + def getpath(pred, v, s): + return [v] if v == s else getpath(pred, pred[v], s) + [v] + + def goldberg_radzik(g, s, t, weight="weight"): + pred, dist = nx.goldberg_radzik(g, s, weight=weight) + dist = dist[t] + return dist, getpath(pred, t, s) + + def astar(g, s, t, weight="weight"): + path = nx.astar_path(g, s, t, heuristic, weight=weight) + dist = nx.astar_path_length(g, s, t, heuristic, weight=weight) + return dist, path + + def vlp(G, s, t, l, F, w): + res = F(G, s, t, weight=w) + validate_length_path(G, s, t, l, *res, weight=w) + + G = self.cycle + s = 6 + t = 4 + path = [6] + list(range(t + 1)) + + def weight(u, v, _): + return 1 + v**2 + + length = sum(weight(u, v, None) for u, v in pairwise(path)) + vlp(G, s, t, length, nx.bidirectional_dijkstra, weight) + vlp(G, s, t, length, nx.single_source_dijkstra, weight) + vlp(G, s, t, length, nx.single_source_bellman_ford, weight) + vlp(G, s, t, length, goldberg_radzik, weight) + vlp(G, s, t, length, astar, weight) + + def weight(u, v, _): + return 2 ** (u * v) + + length = sum(weight(u, v, None) for u, v in pairwise(path)) + vlp(G, s, t, length, nx.bidirectional_dijkstra, weight) + vlp(G, s, t, length, nx.single_source_dijkstra, weight) + vlp(G, s, t, length, nx.single_source_bellman_ford, weight) + vlp(G, s, t, length, goldberg_radzik, weight) + vlp(G, s, t, length, astar, weight) + + def test_bidirectional_dijkstra_no_path(self): + with pytest.raises(nx.NetworkXNoPath): + G = nx.Graph() + nx.add_path(G, [1, 2, 3]) + nx.add_path(G, [4, 5, 6]) + path = nx.bidirectional_dijkstra(G, 1, 6) + + @pytest.mark.parametrize( + "fn", + ( + nx.dijkstra_path, + nx.dijkstra_path_length, + nx.single_source_dijkstra_path, + nx.single_source_dijkstra_path_length, + nx.single_source_dijkstra, + nx.dijkstra_predecessor_and_distance, + ), + ) + def test_absent_source(self, fn): + G = nx.path_graph(2) + with pytest.raises(nx.NodeNotFound): + fn(G, 3, 0) + # Test when source == target, which is handled specially by some functions + with pytest.raises(nx.NodeNotFound): + fn(G, 3, 3) + + def test_dijkstra_predecessor1(self): + G = nx.path_graph(4) + assert nx.dijkstra_predecessor_and_distance(G, 0) == ( + {0: [], 1: [0], 2: [1], 3: [2]}, + {0: 0, 1: 1, 2: 2, 3: 3}, + ) + + def test_dijkstra_predecessor2(self): + # 4-cycle + G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)]) + pred, dist = nx.dijkstra_predecessor_and_distance(G, (0)) + assert pred[0] == [] + assert pred[1] == [0] + assert pred[2] in [[1, 3], [3, 1]] + assert pred[3] == [0] + assert dist == {0: 0, 1: 1, 2: 2, 3: 1} + + def test_dijkstra_predecessor3(self): + XG = nx.DiGraph() + XG.add_weighted_edges_from( + [ + ("s", "u", 10), + ("s", "x", 5), + ("u", "v", 1), + ("u", "x", 2), + ("v", "y", 1), + ("x", "u", 3), + ("x", "v", 5), + ("x", "y", 2), + ("y", "s", 7), + ("y", "v", 6), + ] + ) + (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s") + assert P["v"] == ["u"] + assert D["v"] == 9 + (P, D) = nx.dijkstra_predecessor_and_distance(XG, "s", cutoff=8) + assert "v" not in D + + def test_single_source_dijkstra_path_length(self): + pl = nx.single_source_dijkstra_path_length + assert dict(pl(self.MXG4, 0))[2] == 4 + spl = pl(self.MXG4, 0, cutoff=2) + assert 2 not in spl + + def test_bidirectional_dijkstra_multigraph(self): + G = nx.MultiGraph() + G.add_edge("a", "b", weight=10) + G.add_edge("a", "b", weight=100) + dp = nx.bidirectional_dijkstra(G, "a", "b") + assert dp == (10, ["a", "b"]) + + def test_dijkstra_pred_distance_multigraph(self): + G = nx.MultiGraph() + G.add_edge("a", "b", key="short", foo=5, weight=100) + G.add_edge("a", "b", key="long", bar=1, weight=110) + p, d = nx.dijkstra_predecessor_and_distance(G, "a") + assert p == {"a": [], "b": ["a"]} + assert d == {"a": 0, "b": 100} + + def test_negative_edge_cycle(self): + G = nx.cycle_graph(5, create_using=nx.DiGraph()) + assert not nx.negative_edge_cycle(G) + G.add_edge(8, 9, weight=-7) + G.add_edge(9, 8, weight=3) + graph_size = len(G) + assert nx.negative_edge_cycle(G) + assert graph_size == len(G) + pytest.raises(ValueError, nx.single_source_dijkstra_path_length, G, 8) + pytest.raises(ValueError, nx.single_source_dijkstra, G, 8) + pytest.raises(ValueError, nx.dijkstra_predecessor_and_distance, G, 8) + G.add_edge(9, 10) + pytest.raises(ValueError, nx.bidirectional_dijkstra, G, 8, 10) + G = nx.MultiDiGraph() + G.add_edge(2, 2, weight=-1) + assert nx.negative_edge_cycle(G) + + def test_negative_edge_cycle_empty(self): + G = nx.DiGraph() + assert not nx.negative_edge_cycle(G) + + def test_negative_edge_cycle_custom_weight_key(self): + d = nx.DiGraph() + d.add_edge("a", "b", w=-2) + d.add_edge("b", "a", w=-1) + assert nx.negative_edge_cycle(d, weight="w") + + def test_weight_function(self): + """Tests that a callable weight is interpreted as a weight + function instead of an edge attribute. + + """ + # Create a triangle in which the edge from node 0 to node 2 has + # a large weight and the other two edges have a small weight. + G = nx.complete_graph(3) + G.adj[0][2]["weight"] = 10 + G.adj[0][1]["weight"] = 1 + G.adj[1][2]["weight"] = 1 + + # The weight function will take the multiplicative inverse of + # the weights on the edges. This way, weights that were large + # before now become small and vice versa. + + def weight(u, v, d): + return 1 / d["weight"] + + # The shortest path from 0 to 2 using the actual weights on the + # edges should be [0, 1, 2]. + distance, path = nx.single_source_dijkstra(G, 0, 2) + assert distance == 2 + assert path == [0, 1, 2] + # However, with the above weight function, the shortest path + # should be [0, 2], since that has a very small weight. + distance, path = nx.single_source_dijkstra(G, 0, 2, weight=weight) + assert distance == 1 / 10 + assert path == [0, 2] + + def test_all_pairs_dijkstra_path(self): + cycle = nx.cycle_graph(7) + p = dict(nx.all_pairs_dijkstra_path(cycle)) + assert p[0][3] == [0, 1, 2, 3] + + cycle[1][2]["weight"] = 10 + p = dict(nx.all_pairs_dijkstra_path(cycle)) + assert p[0][3] == [0, 6, 5, 4, 3] + + def test_all_pairs_dijkstra_path_length(self): + cycle = nx.cycle_graph(7) + pl = dict(nx.all_pairs_dijkstra_path_length(cycle)) + assert pl[0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + + cycle[1][2]["weight"] = 10 + pl = dict(nx.all_pairs_dijkstra_path_length(cycle)) + assert pl[0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1} + + def test_all_pairs_dijkstra(self): + cycle = nx.cycle_graph(7) + out = dict(nx.all_pairs_dijkstra(cycle)) + assert out[0][0] == {0: 0, 1: 1, 2: 2, 3: 3, 4: 3, 5: 2, 6: 1} + assert out[0][1][3] == [0, 1, 2, 3] + + cycle[1][2]["weight"] = 10 + out = dict(nx.all_pairs_dijkstra(cycle)) + assert out[0][0] == {0: 0, 1: 1, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1} + assert out[0][1][3] == [0, 6, 5, 4, 3] + + +class TestDijkstraPathLength: + """Unit tests for the :func:`networkx.dijkstra_path_length` + function. + + """ + + def test_weight_function(self): + """Tests for computing the length of the shortest path using + Dijkstra's algorithm with a user-defined weight function. + + """ + # Create a triangle in which the edge from node 0 to node 2 has + # a large weight and the other two edges have a small weight. + G = nx.complete_graph(3) + G.adj[0][2]["weight"] = 10 + G.adj[0][1]["weight"] = 1 + G.adj[1][2]["weight"] = 1 + + # The weight function will take the multiplicative inverse of + # the weights on the edges. This way, weights that were large + # before now become small and vice versa. + + def weight(u, v, d): + return 1 / d["weight"] + + # The shortest path from 0 to 2 using the actual weights on the + # edges should be [0, 1, 2]. However, with the above weight + # function, the shortest path should be [0, 2], since that has a + # very small weight. + length = nx.dijkstra_path_length(G, 0, 2, weight=weight) + assert length == 1 / 10 + + +class TestMultiSourceDijkstra: + """Unit tests for the multi-source dialect of Dijkstra's shortest + path algorithms. + + """ + + def test_no_sources(self): + with pytest.raises(ValueError): + nx.multi_source_dijkstra(nx.Graph(), {}) + + def test_path_no_sources(self): + with pytest.raises(ValueError): + nx.multi_source_dijkstra_path(nx.Graph(), {}) + + def test_path_length_no_sources(self): + with pytest.raises(ValueError): + nx.multi_source_dijkstra_path_length(nx.Graph(), {}) + + @pytest.mark.parametrize( + "fn", + ( + nx.multi_source_dijkstra_path, + nx.multi_source_dijkstra_path_length, + nx.multi_source_dijkstra, + ), + ) + def test_absent_source(self, fn): + G = nx.path_graph(2) + with pytest.raises(nx.NodeNotFound): + fn(G, [3], 0) + with pytest.raises(nx.NodeNotFound): + fn(G, [3], 3) + + def test_two_sources(self): + edges = [(0, 1, 1), (1, 2, 1), (2, 3, 10), (3, 4, 1)] + G = nx.Graph() + G.add_weighted_edges_from(edges) + sources = {0, 4} + distances, paths = nx.multi_source_dijkstra(G, sources) + expected_distances = {0: 0, 1: 1, 2: 2, 3: 1, 4: 0} + expected_paths = {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [4, 3], 4: [4]} + assert distances == expected_distances + assert paths == expected_paths + + def test_simple_paths(self): + G = nx.path_graph(4) + lengths = nx.multi_source_dijkstra_path_length(G, [0]) + assert lengths == {n: n for n in G} + paths = nx.multi_source_dijkstra_path(G, [0]) + assert paths == {n: list(range(n + 1)) for n in G} + + +class TestBellmanFordAndGoldbergRadzik(WeightedTestBase): + def test_single_node_graph(self): + G = nx.DiGraph() + G.add_node(0) + assert nx.single_source_bellman_ford_path(G, 0) == {0: [0]} + assert nx.single_source_bellman_ford_path_length(G, 0) == {0: 0} + assert nx.single_source_bellman_ford(G, 0) == ({0: 0}, {0: [0]}) + assert nx.bellman_ford_predecessor_and_distance(G, 0) == ({0: []}, {0: 0}) + assert nx.goldberg_radzik(G, 0) == ({0: None}, {0: 0}) + + def test_absent_source_bellman_ford(self): + # the check is in _bellman_ford; this provides regression testing + # against later changes to "client" Bellman-Ford functions + G = nx.path_graph(2) + for fn in ( + nx.bellman_ford_predecessor_and_distance, + nx.bellman_ford_path, + nx.bellman_ford_path_length, + nx.single_source_bellman_ford_path, + nx.single_source_bellman_ford_path_length, + nx.single_source_bellman_ford, + ): + pytest.raises(nx.NodeNotFound, fn, G, 3, 0) + pytest.raises(nx.NodeNotFound, fn, G, 3, 3) + + def test_absent_source_goldberg_radzik(self): + with pytest.raises(nx.NodeNotFound): + G = nx.path_graph(2) + nx.goldberg_radzik(G, 3, 0) + + def test_negative_cycle_heuristic(self): + G = nx.DiGraph() + G.add_edge(0, 1, weight=-1) + G.add_edge(1, 2, weight=-1) + G.add_edge(2, 3, weight=-1) + G.add_edge(3, 0, weight=3) + assert not nx.negative_edge_cycle(G, heuristic=True) + G.add_edge(2, 0, weight=1.999) + assert nx.negative_edge_cycle(G, heuristic=True) + G.edges[2, 0]["weight"] = 2 + assert not nx.negative_edge_cycle(G, heuristic=True) + + def test_negative_cycle_consistency(self): + import random + + unif = random.uniform + for random_seed in range(2): # range(20): + random.seed(random_seed) + for density in [0.1, 0.9]: # .3, .7, .9]: + for N in [1, 10, 20]: # range(1, 60 - int(30 * density)): + for max_cost in [1, 90]: # [1, 10, 40, 90]: + G = nx.binomial_graph(N, density, seed=4, directed=True) + edges = ((u, v, unif(-1, max_cost)) for u, v in G.edges) + G.add_weighted_edges_from(edges) + + no_heuristic = nx.negative_edge_cycle(G, heuristic=False) + with_heuristic = nx.negative_edge_cycle(G, heuristic=True) + assert no_heuristic == with_heuristic + + def test_negative_cycle(self): + G = nx.cycle_graph(5, create_using=nx.DiGraph()) + G.add_edge(1, 2, weight=-7) + for i in range(5): + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i + ) + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i + ) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i) + pytest.raises( + nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i + ) + pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i) + G = nx.cycle_graph(5) # undirected Graph + G.add_edge(1, 2, weight=-3) + for i in range(5): + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, i + ) + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, i + ) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, i) + pytest.raises( + nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, i + ) + pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, i) + G = nx.DiGraph([(1, 1, {"weight": -1})]) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, 1) + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, 1 + ) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, 1) + pytest.raises( + nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1 + ) + pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1) + G = nx.MultiDiGraph([(1, 1, {"weight": -1})]) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford_path, G, 1) + pytest.raises( + nx.NetworkXUnbounded, nx.single_source_bellman_ford_path_length, G, 1 + ) + pytest.raises(nx.NetworkXUnbounded, nx.single_source_bellman_ford, G, 1) + pytest.raises( + nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1 + ) + pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1) + + def test_zero_cycle(self): + G = nx.cycle_graph(5, create_using=nx.DiGraph()) + G.add_edge(2, 3, weight=-4) + # check that zero cycle doesn't raise + nx.goldberg_radzik(G, 1) + nx.bellman_ford_predecessor_and_distance(G, 1) + + G.add_edge(2, 3, weight=-4.0001) + # check that negative cycle does raise + pytest.raises( + nx.NetworkXUnbounded, nx.bellman_ford_predecessor_and_distance, G, 1 + ) + pytest.raises(nx.NetworkXUnbounded, nx.goldberg_radzik, G, 1) + + def test_find_negative_cycle_longer_cycle(self): + G = nx.cycle_graph(5, create_using=nx.DiGraph()) + nx.add_cycle(G, [3, 5, 6, 7, 8, 9]) + G.add_edge(1, 2, weight=-30) + assert nx.find_negative_cycle(G, 1) == [0, 1, 2, 3, 4, 0] + assert nx.find_negative_cycle(G, 7) == [2, 3, 4, 0, 1, 2] + + def test_find_negative_cycle_no_cycle(self): + G = nx.path_graph(5, create_using=nx.DiGraph()) + pytest.raises(nx.NetworkXError, nx.find_negative_cycle, G, 3) + + def test_find_negative_cycle_single_edge(self): + G = nx.Graph() + G.add_edge(0, 1, weight=-1) + assert nx.find_negative_cycle(G, 1) == [1, 0, 1] + + def test_negative_weight(self): + G = nx.cycle_graph(5, create_using=nx.DiGraph()) + G.add_edge(1, 2, weight=-3) + assert nx.single_source_bellman_ford_path(G, 0) == { + 0: [0], + 1: [0, 1], + 2: [0, 1, 2], + 3: [0, 1, 2, 3], + 4: [0, 1, 2, 3, 4], + } + assert nx.single_source_bellman_ford_path_length(G, 0) == { + 0: 0, + 1: 1, + 2: -2, + 3: -1, + 4: 0, + } + assert nx.single_source_bellman_ford(G, 0) == ( + {0: 0, 1: 1, 2: -2, 3: -1, 4: 0}, + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3], 4: [0, 1, 2, 3, 4]}, + ) + assert nx.bellman_ford_predecessor_and_distance(G, 0) == ( + {0: [], 1: [0], 2: [1], 3: [2], 4: [3]}, + {0: 0, 1: 1, 2: -2, 3: -1, 4: 0}, + ) + assert nx.goldberg_radzik(G, 0) == ( + {0: None, 1: 0, 2: 1, 3: 2, 4: 3}, + {0: 0, 1: 1, 2: -2, 3: -1, 4: 0}, + ) + + def test_not_connected(self): + G = nx.complete_graph(6) + G.add_edge(10, 11) + G.add_edge(10, 12) + assert nx.single_source_bellman_ford_path(G, 0) == { + 0: [0], + 1: [0, 1], + 2: [0, 2], + 3: [0, 3], + 4: [0, 4], + 5: [0, 5], + } + assert nx.single_source_bellman_ford_path_length(G, 0) == { + 0: 0, + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + } + assert nx.single_source_bellman_ford(G, 0) == ( + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]}, + ) + assert nx.bellman_ford_predecessor_and_distance(G, 0) == ( + {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + ) + assert nx.goldberg_radzik(G, 0) == ( + {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + ) + + # not connected, with a component not containing the source that + # contains a negative cycle. + G = nx.complete_graph(6) + G.add_edges_from( + [ + ("A", "B", {"load": 3}), + ("B", "C", {"load": -10}), + ("C", "A", {"load": 2}), + ] + ) + assert nx.single_source_bellman_ford_path(G, 0, weight="load") == { + 0: [0], + 1: [0, 1], + 2: [0, 2], + 3: [0, 3], + 4: [0, 4], + 5: [0, 5], + } + assert nx.single_source_bellman_ford_path_length(G, 0, weight="load") == { + 0: 0, + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + } + assert nx.single_source_bellman_ford(G, 0, weight="load") == ( + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + {0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5]}, + ) + assert nx.bellman_ford_predecessor_and_distance(G, 0, weight="load") == ( + {0: [], 1: [0], 2: [0], 3: [0], 4: [0], 5: [0]}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + ) + assert nx.goldberg_radzik(G, 0, weight="load") == ( + {0: None, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}, + {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1}, + ) + + def test_multigraph(self): + assert nx.bellman_ford_path(self.MXG, "s", "v") == ["s", "x", "u", "v"] + assert nx.bellman_ford_path_length(self.MXG, "s", "v") == 9 + assert nx.single_source_bellman_ford_path(self.MXG, "s")["v"] == [ + "s", + "x", + "u", + "v", + ] + assert nx.single_source_bellman_ford_path_length(self.MXG, "s")["v"] == 9 + D, P = nx.single_source_bellman_ford(self.MXG, "s", target="v") + assert D == 9 + assert P == ["s", "x", "u", "v"] + P, D = nx.bellman_ford_predecessor_and_distance(self.MXG, "s") + assert P["v"] == ["u"] + assert D["v"] == 9 + P, D = nx.goldberg_radzik(self.MXG, "s") + assert P["v"] == "u" + assert D["v"] == 9 + assert nx.bellman_ford_path(self.MXG4, 0, 2) == [0, 1, 2] + assert nx.bellman_ford_path_length(self.MXG4, 0, 2) == 4 + assert nx.single_source_bellman_ford_path(self.MXG4, 0)[2] == [0, 1, 2] + assert nx.single_source_bellman_ford_path_length(self.MXG4, 0)[2] == 4 + D, P = nx.single_source_bellman_ford(self.MXG4, 0, target=2) + assert D == 4 + assert P == [0, 1, 2] + P, D = nx.bellman_ford_predecessor_and_distance(self.MXG4, 0) + assert P[2] == [1] + assert D[2] == 4 + P, D = nx.goldberg_radzik(self.MXG4, 0) + assert P[2] == 1 + assert D[2] == 4 + + def test_others(self): + assert nx.bellman_ford_path(self.XG, "s", "v") == ["s", "x", "u", "v"] + assert nx.bellman_ford_path_length(self.XG, "s", "v") == 9 + assert nx.single_source_bellman_ford_path(self.XG, "s")["v"] == [ + "s", + "x", + "u", + "v", + ] + assert nx.single_source_bellman_ford_path_length(self.XG, "s")["v"] == 9 + D, P = nx.single_source_bellman_ford(self.XG, "s", target="v") + assert D == 9 + assert P == ["s", "x", "u", "v"] + (P, D) = nx.bellman_ford_predecessor_and_distance(self.XG, "s") + assert P["v"] == ["u"] + assert D["v"] == 9 + (P, D) = nx.goldberg_radzik(self.XG, "s") + assert P["v"] == "u" + assert D["v"] == 9 + + def test_path_graph(self): + G = nx.path_graph(4) + assert nx.single_source_bellman_ford_path(G, 0) == { + 0: [0], + 1: [0, 1], + 2: [0, 1, 2], + 3: [0, 1, 2, 3], + } + assert nx.single_source_bellman_ford_path_length(G, 0) == { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + } + assert nx.single_source_bellman_ford(G, 0) == ( + {0: 0, 1: 1, 2: 2, 3: 3}, + {0: [0], 1: [0, 1], 2: [0, 1, 2], 3: [0, 1, 2, 3]}, + ) + assert nx.bellman_ford_predecessor_and_distance(G, 0) == ( + {0: [], 1: [0], 2: [1], 3: [2]}, + {0: 0, 1: 1, 2: 2, 3: 3}, + ) + assert nx.goldberg_radzik(G, 0) == ( + {0: None, 1: 0, 2: 1, 3: 2}, + {0: 0, 1: 1, 2: 2, 3: 3}, + ) + assert nx.single_source_bellman_ford_path(G, 3) == { + 0: [3, 2, 1, 0], + 1: [3, 2, 1], + 2: [3, 2], + 3: [3], + } + assert nx.single_source_bellman_ford_path_length(G, 3) == { + 0: 3, + 1: 2, + 2: 1, + 3: 0, + } + assert nx.single_source_bellman_ford(G, 3) == ( + {0: 3, 1: 2, 2: 1, 3: 0}, + {0: [3, 2, 1, 0], 1: [3, 2, 1], 2: [3, 2], 3: [3]}, + ) + assert nx.bellman_ford_predecessor_and_distance(G, 3) == ( + {0: [1], 1: [2], 2: [3], 3: []}, + {0: 3, 1: 2, 2: 1, 3: 0}, + ) + assert nx.goldberg_radzik(G, 3) == ( + {0: 1, 1: 2, 2: 3, 3: None}, + {0: 3, 1: 2, 2: 1, 3: 0}, + ) + + def test_4_cycle(self): + # 4-cycle + G = nx.Graph([(0, 1), (1, 2), (2, 3), (3, 0)]) + dist, path = nx.single_source_bellman_ford(G, 0) + assert dist == {0: 0, 1: 1, 2: 2, 3: 1} + assert path[0] == [0] + assert path[1] == [0, 1] + assert path[2] in [[0, 1, 2], [0, 3, 2]] + assert path[3] == [0, 3] + + pred, dist = nx.bellman_ford_predecessor_and_distance(G, 0) + assert pred[0] == [] + assert pred[1] == [0] + assert pred[2] in [[1, 3], [3, 1]] + assert pred[3] == [0] + assert dist == {0: 0, 1: 1, 2: 2, 3: 1} + + pred, dist = nx.goldberg_radzik(G, 0) + assert pred[0] is None + assert pred[1] == 0 + assert pred[2] in [1, 3] + assert pred[3] == 0 + assert dist == {0: 0, 1: 1, 2: 2, 3: 1} + + def test_negative_weight_bf_path(self): + G = nx.DiGraph() + G.add_nodes_from("abcd") + G.add_edge("a", "d", weight=0) + G.add_edge("a", "b", weight=1) + G.add_edge("b", "c", weight=-3) + G.add_edge("c", "d", weight=1) + + assert nx.bellman_ford_path(G, "a", "d") == ["a", "b", "c", "d"] + assert nx.bellman_ford_path_length(G, "a", "d") == -1 + + def test_zero_cycle_smoke(self): + D = nx.DiGraph() + D.add_weighted_edges_from([(0, 1, 1), (1, 2, 1), (2, 3, 1), (3, 1, -2)]) + + nx.bellman_ford_path(D, 1, 3) + nx.dijkstra_path(D, 1, 3) + nx.bidirectional_dijkstra(D, 1, 3) + # FIXME nx.goldberg_radzik(D, 1) + + def test_skip_visited_unweighted(self): + """Check that `goldberg_radzik` correctly skips visited nodes in `topo_sort`. + + This doesn't reliably get tested by other tests because iterating over + the `relabeled` set is not deterministic. + """ + G = nx.Graph([(0, 4), (0, 5), (1, 3), (1, 4), (2, 3), (2, 5), (3, 5), (3, 6)]) + + _, dist = nx.goldberg_radzik(G, 4) + assert dist == {0: 1, 1: 1, 2: 3, 3: 2, 4: 0, 5: 2, 6: 3} + + +class TestJohnsonAlgorithm(WeightedTestBase): + def test_single_node_graph(self): + G = nx.DiGraph() + G.add_node(0) + assert nx.johnson(G) == {0: {0: [0]}} + + def test_negative_cycle(self): + G = nx.DiGraph() + G.add_weighted_edges_from( + [ + ("0", "3", 3), + ("0", "1", -5), + ("1", "0", -5), + ("0", "2", 2), + ("1", "2", 4), + ("2", "3", 1), + ] + ) + pytest.raises(nx.NetworkXUnbounded, nx.johnson, G) + G = nx.Graph() + G.add_weighted_edges_from( + [ + ("0", "3", 3), + ("0", "1", -5), + ("1", "0", -5), + ("0", "2", 2), + ("1", "2", 4), + ("2", "3", 1), + ] + ) + pytest.raises(nx.NetworkXUnbounded, nx.johnson, G) + + def test_negative_weights(self): + G = nx.DiGraph() + G.add_weighted_edges_from( + [("0", "3", 3), ("0", "1", -5), ("0", "2", 2), ("1", "2", 4), ("2", "3", 1)] + ) + paths = nx.johnson(G) + assert paths == { + "1": {"1": ["1"], "3": ["1", "2", "3"], "2": ["1", "2"]}, + "0": { + "1": ["0", "1"], + "0": ["0"], + "3": ["0", "1", "2", "3"], + "2": ["0", "1", "2"], + }, + "3": {"3": ["3"]}, + "2": {"3": ["2", "3"], "2": ["2"]}, + } + + def test_unweighted_graph(self): + G = nx.Graph() + G.add_edges_from([(1, 0), (2, 1)]) + H = G.copy() + nx.set_edge_attributes(H, values=1, name="weight") + assert nx.johnson(G) == nx.johnson(H) + + def test_partially_weighted_graph_with_negative_edges(self): + G = nx.DiGraph() + G.add_edges_from([(0, 1), (1, 2), (2, 0), (1, 0)]) + G[1][0]["weight"] = -2 + G[0][1]["weight"] = 3 + G[1][2]["weight"] = -4 + + H = G.copy() + H[2][0]["weight"] = 1 + + I = G.copy() + I[2][0]["weight"] = 8 + + assert nx.johnson(G) == nx.johnson(H) + assert nx.johnson(G) != nx.johnson(I) + + def test_graphs(self): + validate_path(self.XG, "s", "v", 9, nx.johnson(self.XG)["s"]["v"]) + validate_path(self.MXG, "s", "v", 9, nx.johnson(self.MXG)["s"]["v"]) + validate_path(self.XG2, 1, 3, 4, nx.johnson(self.XG2)[1][3]) + validate_path(self.XG3, 0, 3, 15, nx.johnson(self.XG3)[0][3]) + validate_path(self.XG4, 0, 2, 4, nx.johnson(self.XG4)[0][2]) + validate_path(self.MXG4, 0, 2, 4, nx.johnson(self.MXG4)[0][2]) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6fa4406338006f8211c565f562122e4caeac6b81 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_asteroidal.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_asteroidal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7633d58e638c1d97d84169e956e84714f2918106 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_asteroidal.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_boundary.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_boundary.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c8a671c5aaf9cdc030207050cfe0ae94efc023f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_boundary.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_bridges.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_bridges.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7b056f4858823500d080104cfc8ce11196a0826 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_bridges.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_broadcasting.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_broadcasting.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a96f63eaed4fa3363290794e1e92fdd61c5b278 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_broadcasting.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chains.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chains.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3adc54d91ba56f35ddc6aebe17b678bec2e57680 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chains.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chordal.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chordal.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47e5c0ba25449e6e691a254735aa6d6fd9590c15 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_chordal.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_clique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_clique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9f958ea27e06e5bf2d6554f846671569be284ed Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_clique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cluster.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cluster.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1066d37c3389e5a7271ad8c1c294d42ae9b9f972 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cluster.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_communicability.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_communicability.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe634b4bc39eb5e535e9a1ee487d099a930e9f22 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_communicability.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_core.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bebb3aa457fa5cdaa312793e3d273144b77cf06b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_core.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_covering.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_covering.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c5badee10bd50dd1105bf9fad5bf52a02a2a7684 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_covering.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cuts.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cuts.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd5d2755ed1df588e043df7a5664c4353a113cb2 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cuts.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cycles.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cycles.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..513a6c38cdfa88c79e1a1882af961acc64829118 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_cycles.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_d_separation.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_d_separation.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..456b613b5963a7e74df6e2103e450c62c164cdfa Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_d_separation.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dag.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f3809b4ba7c917f5f11acd8a03800e1f3cc84b12 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dag.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..374a2eb35e8295f6fb93e41f1d1aca4ac092450f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_regular.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_regular.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e0be3f2fd63f668c680caed5c5956b376fdb699 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_distance_regular.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominance.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominance.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3538ef0c91a00ade47734dc626f6d3056c1b43f5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominance.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominating.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominating.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb10e17fb1442cc0eaee7acb4bf11d8ecd09061c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_dominating.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_efficiency.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_efficiency.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82bd26d174193b0882c1ac87edf51d7d9f88fd59 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_efficiency.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_euler.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_euler.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2000c4b52dc6013a2fc1a7e7fbb48fafbbeec899 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_euler.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graph_hashing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graph_hashing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88ebcfd10433519fb5b96837d2a17a872df901ef Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graph_hashing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graphical.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graphical.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1a0a098d656253a9140440c06bd7fcef9485e0f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_graphical.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hierarchy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hierarchy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1fcf19d8485f329dbee4e430a988da9b03fff99 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hierarchy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hybrid.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hybrid.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..971c087e41acc1294ff0d358467a4b7cb405e92c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_hybrid.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_isolate.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_isolate.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be8a03cedfe9b386ea5f2d04708dbbc62d5de982 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_isolate.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_link_prediction.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_link_prediction.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ddf3c735d69c6ea61ef9e4fbaac8ef73045706b6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_link_prediction.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_lowest_common_ancestors.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_lowest_common_ancestors.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8ec50bfab9848e6c5591d4985aef1fa06afe7ab Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_lowest_common_ancestors.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_matching.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_matching.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7ef227c7dbef73b85dc147f933533d01891f41e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_matching.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_max_weight_clique.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_max_weight_clique.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..daab87120a165aa5a08287623c8c11022228147e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_max_weight_clique.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_mis.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_mis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9681f1e0ee240359a8de34ef90f0e2ff8fa56cb1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_mis.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_moral.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_moral.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ff20f73941332b88330ff7e9d9e3f3cd5e9cc6d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_moral.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_node_classification.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_node_classification.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3caca3d52ab0728bde0dc11bfa60f4a5641d5141 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_node_classification.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_non_randomness.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_non_randomness.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2cd271a7cf0caf0c983ecedf5d4695856a2c77af Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_non_randomness.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_perfect_graph.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_perfect_graph.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e38b060108c91b66791b5a60b8e0225a64fd040 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_perfect_graph.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planar_drawing.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planar_drawing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66747de023f845eb236cad068c44f81b6312f9df Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planar_drawing.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planarity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planarity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c173f681a44773f4343a272cd1ad0440fe81d84 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_planarity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_polynomials.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_polynomials.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f45f4834c874f62fb7e3c3490374ca0ee5b6558d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_polynomials.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_reciprocity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_reciprocity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9ec2ef74d07bf60d18256737b4f9d12ed62a39af Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_reciprocity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_regular.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_regular.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b880effeb482b23d28048f556076cfbea237edf Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_regular.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_richclub.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_richclub.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5da1b981b400310a40cb8223ff245946c92ca118 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_richclub.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_similarity.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_similarity.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df260a6ad0cefa96bd770970640b5bec86407924 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_similarity.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_simple_paths.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_simple_paths.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4b1651ef0aa0f854b023765d2bf3b3ebe958ee1 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_simple_paths.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smallworld.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smallworld.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e762ef979eaf08a84256c56978f606d7a23655b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smallworld.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smetric.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smetric.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..703ae347489ef67195b36d7c98ec932a5338bb08 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_smetric.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_sparsifiers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_sparsifiers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a8cc960646061f0296a07455d21712bd8142723 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_sparsifiers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_structuralholes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_structuralholes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47bbbd128a1aef219276d8665e59eb7e9d84c7e9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_structuralholes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_summarization.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_summarization.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c5f8915e5bbf4396d851aaebe5e46d3ff5fa688 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_summarization.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_swap.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_swap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ba2bd61ac75ebe76de9e04fd02caf434dd3fc80 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_swap.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_threshold.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_threshold.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e747fcc63aed6f47f850508a203746f9852f6d7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_threshold.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_time_dependent.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_time_dependent.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4b7203a111dc519d65b54a07af2f72528cdacbc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_time_dependent.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_tournament.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_tournament.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc6b92be3270e124e99bdca7f34281a6eff97fac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_tournament.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_triads.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_triads.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d738358ca3b78a5ab337e2de1c718ad72a2b8edd Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_triads.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_vitality.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_vitality.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82a99b467d8ac625bc771fe6e7efc2d611557283 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_vitality.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_voronoi.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_voronoi.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79692a20388e5cbb9da439cefb04302250507d70 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_voronoi.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_walks.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_walks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb58b79162ad61190d48658d202ffce9dd1cac05 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_walks.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_wiener.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_wiener.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a229ad38aaa0237566e08a84200bc59754f512e8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tests/__pycache__/test_wiener.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..93e6cdd08ca959835cc5c5f9a3e6dc353f4b217d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__init__.py @@ -0,0 +1,5 @@ +from .beamsearch import * +from .breadth_first_search import * +from .depth_first_search import * +from .edgedfs import * +from .edgebfs import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13039339b958b02719dd915558fb97ece068f265 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0b072cf56a0409804e553282ea31a30fb341356c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/beamsearch.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3fd511998bdc25fd3b679ff1c87aed95189144bc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/breadth_first_search.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6613904f2f8586519b452d68977e009d9adc2a3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/depth_first_search.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32f5431871b70b41a87e2d90c164827418fed721 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgebfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99453b5819a931bc7f35d0a1ead50220983f5efc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/__pycache__/edgedfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/beamsearch.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/beamsearch.py new file mode 100644 index 0000000000000000000000000000000000000000..23fbe7bbb3f037eca4f7ede25b7edf6305356587 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/beamsearch.py @@ -0,0 +1,90 @@ +"""Basic algorithms for breadth-first searching the nodes of a graph.""" + +import networkx as nx + +__all__ = ["bfs_beam_edges"] + + +@nx._dispatchable +def bfs_beam_edges(G, source, value, width=None): + """Iterates over edges in a beam search. + + The beam search is a generalized breadth-first search in which only + the "best" *w* neighbors of the current node are enqueued, where *w* + is the beam width and "best" is an application-specific + heuristic. In general, a beam search with a small beam width might + not visit each node in the graph. + + .. note:: + + With the default value of ``width=None`` or `width` greater than the + maximum degree of the graph, this function equates to a slower + version of `~networkx.algorithms.traversal.breadth_first_search.bfs_edges`. + All nodes will be visited, though the order of the reported edges may + vary. In such cases, `value` has no effect - consider using `bfs_edges` + directly instead. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for the breadth-first search; this function + iterates over only those edges in the component reachable from + this node. + + value : function + A function that takes a node of the graph as input and returns a + real number indicating how "good" it is. A higher value means it + is more likely to be visited sooner during the search. When + visiting a new node, only the `width` neighbors with the highest + `value` are enqueued (in decreasing order of `value`). + + width : int (default = None) + The beam width for the search. This is the number of neighbors + (ordered by `value`) to enqueue when visiting each new node. + + Yields + ------ + edge + Edges in the beam search starting from `source`, given as a pair + of nodes. + + Examples + -------- + To give nodes with, for example, a higher centrality precedence + during the search, set the `value` function to return the centrality + value of the node: + + >>> G = nx.karate_club_graph() + >>> centrality = nx.eigenvector_centrality(G) + >>> list(nx.bfs_beam_edges(G, source=0, value=centrality.get, width=3)) + [(0, 2), (0, 1), (0, 8), (2, 32), (1, 13), (8, 33)] + """ + + if width is None: + width = len(G) + + def successors(v): + """Returns a list of the best neighbors of a node. + + `v` is a node in the graph `G`. + + The "best" neighbors are chosen according to the `value` + function (higher is better). Only the `width` best neighbors of + `v` are returned. + """ + # TODO The Python documentation states that for small values, it + # is better to use `heapq.nlargest`. We should determine the + # threshold at which its better to use `heapq.nlargest()` + # instead of `sorted()[:]` and apply that optimization here. + # + # If `width` is greater than the number of neighbors of `v`, all + # neighbors are returned by the semantics of slicing in + # Python. This occurs in the special case that the user did not + # specify a `width`: in this case all neighbors are always + # returned, so this is just a (slower) implementation of + # `bfs_edges(G, source)` but with a sorted enqueue step. + return iter(sorted(G.neighbors(v), key=value, reverse=True)[:width]) + + yield from nx.generic_bfs_edges(G, source, successors) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/breadth_first_search.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/breadth_first_search.py new file mode 100644 index 0000000000000000000000000000000000000000..71076ebb03c445ca842b040ff86e3a2d4e719af1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/breadth_first_search.py @@ -0,0 +1,576 @@ +"""Basic algorithms for breadth-first searching the nodes of a graph.""" + +from collections import deque + +import networkx as nx + +__all__ = [ + "bfs_edges", + "bfs_tree", + "bfs_predecessors", + "bfs_successors", + "descendants_at_distance", + "bfs_layers", + "bfs_labeled_edges", + "generic_bfs_edges", +] + + +@nx._dispatchable +def generic_bfs_edges(G, source, neighbors=None, depth_limit=None): + """Iterate over edges in a breadth-first search. + + The breadth-first search begins at `source` and enqueues the + neighbors of newly visited nodes specified by the `neighbors` + function. + + Parameters + ---------- + G : NetworkX graph + + source : node + Starting node for the breadth-first search; this function + iterates over only those edges in the component reachable from + this node. + + neighbors : function + A function that takes a newly visited node of the graph as input + and returns an *iterator* (not just a list) of nodes that are + neighbors of that node with custom ordering. If not specified, this is + just the ``G.neighbors`` method, but in general it can be any function + that returns an iterator over some or all of the neighbors of a + given node, in any order. + + depth_limit : int, optional(default=len(G)) + Specify the maximum search depth. + + Yields + ------ + edge + Edges in the breadth-first search starting from `source`. + + Examples + -------- + >>> G = nx.path_graph(7) + >>> list(nx.generic_bfs_edges(G, source=0)) + [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] + >>> list(nx.generic_bfs_edges(G, source=2)) + [(2, 1), (2, 3), (1, 0), (3, 4), (4, 5), (5, 6)] + >>> list(nx.generic_bfs_edges(G, source=2, depth_limit=2)) + [(2, 1), (2, 3), (1, 0), (3, 4)] + + The `neighbors` param can be used to specify the visitation order of each + node's neighbors generically. In the following example, we modify the default + neighbor to return *odd* nodes first: + + >>> def odd_first(n): + ... return sorted(G.neighbors(n), key=lambda x: x % 2, reverse=True) + + >>> G = nx.star_graph(5) + >>> list(nx.generic_bfs_edges(G, source=0)) # Default neighbor ordering + [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)] + >>> list(nx.generic_bfs_edges(G, source=0, neighbors=odd_first)) + [(0, 1), (0, 3), (0, 5), (0, 2), (0, 4)] + + Notes + ----- + This implementation is from `PADS`_, which was in the public domain + when it was first accessed in July, 2004. The modifications + to allow depth limits are based on the Wikipedia article + "`Depth-limited-search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS/BFS.py + .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search + """ + if neighbors is None: + neighbors = G.neighbors + if depth_limit is None: + depth_limit = len(G) + + seen = {source} + n = len(G) + depth = 0 + next_parents_children = [(source, neighbors(source))] + while next_parents_children and depth < depth_limit: + this_parents_children = next_parents_children + next_parents_children = [] + for parent, children in this_parents_children: + for child in children: + if child not in seen: + seen.add(child) + next_parents_children.append((child, neighbors(child))) + yield parent, child + if len(seen) == n: + return + depth += 1 + + +@nx._dispatchable +def bfs_edges(G, source, reverse=False, depth_limit=None, sort_neighbors=None): + """Iterate over edges in a breadth-first-search starting at source. + + Parameters + ---------- + G : NetworkX graph + + source : node + Specify starting node for breadth-first search; this function + iterates over only those edges in the component reachable from + this node. + + reverse : bool, optional + If True traverse a directed graph in the reverse direction + + depth_limit : int, optional(default=len(G)) + Specify the maximum search depth + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Yields + ------ + edge: 2-tuple of nodes + Yields edges resulting from the breadth-first search. + + Examples + -------- + To get the edges in a breadth-first search: + + >>> G = nx.path_graph(3) + >>> list(nx.bfs_edges(G, 0)) + [(0, 1), (1, 2)] + >>> list(nx.bfs_edges(G, source=0, depth_limit=1)) + [(0, 1)] + + To get the nodes in a breadth-first search order: + + >>> G = nx.path_graph(3) + >>> root = 2 + >>> edges = nx.bfs_edges(G, root) + >>> nodes = [root] + [v for u, v in edges] + >>> nodes + [2, 1, 0] + + Notes + ----- + The naming of this function is very similar to + :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs`. The difference + is that ``edge_bfs`` yields edges even if they extend back to an already + explored node while this generator yields the edges of the tree that results + from a breadth-first-search (BFS) so no edges are reported if they extend + to already explored nodes. That means ``edge_bfs`` reports all edges while + ``bfs_edges`` only reports those traversed by a node-based BFS. Yet another + description is that ``bfs_edges`` reports the edges traversed during BFS + while ``edge_bfs`` reports all edges in the order they are explored. + + Based on the breadth-first search implementation in PADS [1]_ + by D. Eppstein, July 2004; with modifications to allow depth limits + as described in [2]_. + + References + ---------- + .. [1] http://www.ics.uci.edu/~eppstein/PADS/BFS.py. + .. [2] https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + bfs_tree + :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges` + :func:`~networkx.algorithms.traversal.edgebfs.edge_bfs` + + """ + if reverse and G.is_directed(): + successors = G.predecessors + else: + successors = G.neighbors + + if sort_neighbors is not None: + yield from generic_bfs_edges( + G, source, lambda node: iter(sort_neighbors(successors(node))), depth_limit + ) + else: + yield from generic_bfs_edges(G, source, successors, depth_limit) + + +@nx._dispatchable(returns_graph=True) +def bfs_tree(G, source, reverse=False, depth_limit=None, sort_neighbors=None): + """Returns an oriented tree constructed from of a breadth-first-search + starting at source. + + Parameters + ---------- + G : NetworkX graph + + source : node + Specify starting node for breadth-first search + + reverse : bool, optional + If True traverse a directed graph in the reverse direction + + depth_limit : int, optional(default=len(G)) + Specify the maximum search depth + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + T: NetworkX DiGraph + An oriented tree + + Examples + -------- + >>> G = nx.path_graph(3) + >>> list(nx.bfs_tree(G, 1).edges()) + [(1, 0), (1, 2)] + >>> H = nx.Graph() + >>> nx.add_path(H, [0, 1, 2, 3, 4, 5, 6]) + >>> nx.add_path(H, [2, 7, 8, 9, 10]) + >>> sorted(list(nx.bfs_tree(H, source=3, depth_limit=3).edges())) + [(1, 0), (2, 1), (2, 7), (3, 2), (3, 4), (4, 5), (5, 6), (7, 8)] + + + Notes + ----- + Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py + by D. Eppstein, July 2004. The modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited-search`_". + + .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_tree + bfs_edges + edge_bfs + """ + T = nx.DiGraph() + T.add_node(source) + edges_gen = bfs_edges( + G, + source, + reverse=reverse, + depth_limit=depth_limit, + sort_neighbors=sort_neighbors, + ) + T.add_edges_from(edges_gen) + return T + + +@nx._dispatchable +def bfs_predecessors(G, source, depth_limit=None, sort_neighbors=None): + """Returns an iterator of predecessors in breadth-first-search from source. + + Parameters + ---------- + G : NetworkX graph + + source : node + Specify starting node for breadth-first search + + depth_limit : int, optional(default=len(G)) + Specify the maximum search depth + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + pred: iterator + (node, predecessor) iterator where `predecessor` is the predecessor of + `node` in a breadth first search starting from `source`. + + Examples + -------- + >>> G = nx.path_graph(3) + >>> dict(nx.bfs_predecessors(G, 0)) + {1: 0, 2: 1} + >>> H = nx.Graph() + >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) + >>> dict(nx.bfs_predecessors(H, 0)) + {1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2} + >>> M = nx.Graph() + >>> nx.add_path(M, [0, 1, 2, 3, 4, 5, 6]) + >>> nx.add_path(M, [2, 7, 8, 9, 10]) + >>> sorted(nx.bfs_predecessors(M, source=1, depth_limit=3)) + [(0, 1), (2, 1), (3, 2), (4, 3), (7, 2), (8, 7)] + >>> N = nx.DiGraph() + >>> nx.add_path(N, [0, 1, 2, 3, 4, 7]) + >>> nx.add_path(N, [3, 5, 6, 7]) + >>> sorted(nx.bfs_predecessors(N, source=2)) + [(3, 2), (4, 3), (5, 3), (6, 5), (7, 4)] + + Notes + ----- + Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py + by D. Eppstein, July 2004. The modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited-search`_". + + .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + bfs_tree + bfs_edges + edge_bfs + """ + for s, t in bfs_edges( + G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors + ): + yield (t, s) + + +@nx._dispatchable +def bfs_successors(G, source, depth_limit=None, sort_neighbors=None): + """Returns an iterator of successors in breadth-first-search from source. + + Parameters + ---------- + G : NetworkX graph + + source : node + Specify starting node for breadth-first search + + depth_limit : int, optional(default=len(G)) + Specify the maximum search depth + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + succ: iterator + (node, successors) iterator where `successors` is the non-empty list of + successors of `node` in a breadth first search from `source`. + To appear in the iterator, `node` must have successors. + + Examples + -------- + >>> G = nx.path_graph(3) + >>> dict(nx.bfs_successors(G, 0)) + {0: [1], 1: [2]} + >>> H = nx.Graph() + >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) + >>> dict(nx.bfs_successors(H, 0)) + {0: [1, 2], 1: [3, 4], 2: [5, 6]} + >>> G = nx.Graph() + >>> nx.add_path(G, [0, 1, 2, 3, 4, 5, 6]) + >>> nx.add_path(G, [2, 7, 8, 9, 10]) + >>> dict(nx.bfs_successors(G, source=1, depth_limit=3)) + {1: [0, 2], 2: [3, 7], 3: [4], 7: [8]} + >>> G = nx.DiGraph() + >>> nx.add_path(G, [0, 1, 2, 3, 4, 5]) + >>> dict(nx.bfs_successors(G, source=3)) + {3: [4], 4: [5]} + + Notes + ----- + Based on http://www.ics.uci.edu/~eppstein/PADS/BFS.py + by D. Eppstein, July 2004.The modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited-search`_". + + .. _Depth-limited-search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + bfs_tree + bfs_edges + edge_bfs + """ + parent = source + children = [] + for p, c in bfs_edges( + G, source, depth_limit=depth_limit, sort_neighbors=sort_neighbors + ): + if p == parent: + children.append(c) + continue + yield (parent, children) + children = [c] + parent = p + yield (parent, children) + + +@nx._dispatchable +def bfs_layers(G, sources): + """Returns an iterator of all the layers in breadth-first search traversal. + + Parameters + ---------- + G : NetworkX graph + A graph over which to find the layers using breadth-first search. + + sources : node in `G` or iterable of nodes in `G` + Specify starting nodes for single source or multiple sources + breadth-first search. + + Yields + ------ + layer : list of nodes + Yields list of nodes at the same distance from `sources`. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> dict(enumerate(nx.bfs_layers(G, [0, 4]))) + {0: [0, 4], 1: [1, 3], 2: [2]} + >>> H = nx.Graph() + >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) + >>> dict(enumerate(nx.bfs_layers(H, [1]))) + {0: [1], 1: [0, 3, 4], 2: [2], 3: [5, 6]} + >>> dict(enumerate(nx.bfs_layers(H, [1, 6]))) + {0: [1, 6], 1: [0, 3, 4, 2], 2: [5]} + """ + if sources in G: + sources = [sources] + + visited = set(sources) + current_layer = list(visited) + + for source in current_layer: + if source not in G: + raise nx.NetworkXError(f"The node {source} is not in the graph.") + + # this is basically BFS, except that the current layer only stores the nodes at + # same distance from sources at each iteration + while current_layer: + yield current_layer + next_layer = [] + for node in current_layer: + for child in G[node]: + if child not in visited: + visited.add(child) + next_layer.append(child) + current_layer = next_layer + + +REVERSE_EDGE = "reverse" +TREE_EDGE = "tree" +FORWARD_EDGE = "forward" +LEVEL_EDGE = "level" + + +@nx._dispatchable +def bfs_labeled_edges(G, sources): + """Iterate over edges in a breadth-first search (BFS) labeled by type. + + We generate triple of the form (*u*, *v*, *d*), where (*u*, *v*) is the + edge being explored in the breadth-first search and *d* is one of the + strings 'tree', 'forward', 'level', or 'reverse'. A 'tree' edge is one in + which *v* is first discovered and placed into the layer below *u*. A + 'forward' edge is one in which *u* is on the layer above *v* and *v* has + already been discovered. A 'level' edge is one in which both *u* and *v* + occur on the same layer. A 'reverse' edge is one in which *u* is on a layer + below *v*. + + We emit each edge exactly once. In an undirected graph, 'reverse' edges do + not occur, because each is discovered either as a 'tree' or 'forward' edge. + + Parameters + ---------- + G : NetworkX graph + A graph over which to find the layers using breadth-first search. + + sources : node in `G` or list of nodes in `G` + Starting nodes for single source or multiple sources breadth-first search + + Yields + ------ + edges: generator + A generator of triples (*u*, *v*, *d*) where (*u*, *v*) is the edge being + explored and *d* is described above. + + Examples + -------- + >>> G = nx.cycle_graph(4, create_using=nx.DiGraph) + >>> list(nx.bfs_labeled_edges(G, 0)) + [(0, 1, 'tree'), (1, 2, 'tree'), (2, 3, 'tree'), (3, 0, 'reverse')] + >>> G = nx.complete_graph(3) + >>> list(nx.bfs_labeled_edges(G, 0)) + [(0, 1, 'tree'), (0, 2, 'tree'), (1, 2, 'level')] + >>> list(nx.bfs_labeled_edges(G, [0, 1])) + [(0, 1, 'level'), (0, 2, 'tree'), (1, 2, 'forward')] + """ + if sources in G: + sources = [sources] + + neighbors = G._adj + directed = G.is_directed() + visited = set() + visit = visited.discard if directed else visited.add + # We use visited in a negative sense, so the visited set stays empty for the + # directed case and level edges are reported on their first occurrence in + # the undirected case. Note our use of visited.discard -- this is built-in + # thus somewhat faster than a python-defined def nop(x): pass + depth = {s: 0 for s in sources} + queue = deque(depth.items()) + push = queue.append + pop = queue.popleft + while queue: + u, du = pop() + for v in neighbors[u]: + if v not in depth: + depth[v] = dv = du + 1 + push((v, dv)) + yield u, v, TREE_EDGE + else: + dv = depth[v] + if du == dv: + if v not in visited: + yield u, v, LEVEL_EDGE + elif du < dv: + yield u, v, FORWARD_EDGE + elif directed: + yield u, v, REVERSE_EDGE + visit(u) + + +@nx._dispatchable +def descendants_at_distance(G, source, distance): + """Returns all nodes at a fixed `distance` from `source` in `G`. + + Parameters + ---------- + G : NetworkX graph + A graph + source : node in `G` + distance : the distance of the wanted nodes from `source` + + Returns + ------- + set() + The descendants of `source` in `G` at the given `distance` from `source` + + Examples + -------- + >>> G = nx.path_graph(5) + >>> nx.descendants_at_distance(G, 2, 2) + {0, 4} + >>> H = nx.DiGraph() + >>> H.add_edges_from([(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)]) + >>> nx.descendants_at_distance(H, 0, 2) + {3, 4, 5, 6} + >>> nx.descendants_at_distance(H, 5, 0) + {5} + >>> nx.descendants_at_distance(H, 5, 1) + set() + """ + if source not in G: + raise nx.NetworkXError(f"The node {source} is not in the graph.") + + bfs_generator = nx.bfs_layers(G, source) + for i, layer in enumerate(bfs_generator): + if i == distance: + return set(layer) + return set() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/depth_first_search.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/depth_first_search.py new file mode 100644 index 0000000000000000000000000000000000000000..5bac5ecfd1cbefcba5707cac2885ef32987ee98b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/depth_first_search.py @@ -0,0 +1,529 @@ +"""Basic algorithms for depth-first searching the nodes of a graph.""" + +from collections import defaultdict + +import networkx as nx + +__all__ = [ + "dfs_edges", + "dfs_tree", + "dfs_predecessors", + "dfs_successors", + "dfs_preorder_nodes", + "dfs_postorder_nodes", + "dfs_labeled_edges", +] + + +@nx._dispatchable +def dfs_edges(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Iterate over edges in a depth-first-search (DFS). + + Perform a depth-first-search over the nodes of `G` and yield + the edges in order. This may not generate all edges in `G` + (see `~networkx.algorithms.traversal.edgedfs.edge_dfs`). + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search and yield edges in + the component reachable from source. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Yields + ------ + edge: 2-tuple of nodes + Yields edges resulting from the depth-first-search. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> list(nx.dfs_edges(G, source=0)) + [(0, 1), (1, 2), (2, 3), (3, 4)] + >>> list(nx.dfs_edges(G, source=0, depth_limit=2)) + [(0, 1), (1, 2)] + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in PADS [1]_, with modifications + to allow depth limits based on the Wikipedia article + "Depth-limited search" [2]_. + + See Also + -------- + dfs_preorder_nodes + dfs_postorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs` + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges` + + References + ---------- + .. [1] http://www.ics.uci.edu/~eppstein/PADS + .. [2] https://en.wikipedia.org/wiki/Depth-limited_search + """ + if source is None: + # edges for all components + nodes = G + else: + # edges for components with source + nodes = [source] + if depth_limit is None: + depth_limit = len(G) + + get_children = ( + G.neighbors + if sort_neighbors is None + else lambda n: iter(sort_neighbors(G.neighbors(n))) + ) + + visited = set() + for start in nodes: + if start in visited: + continue + visited.add(start) + stack = [(start, get_children(start))] + depth_now = 1 + while stack: + parent, children = stack[-1] + for child in children: + if child not in visited: + yield parent, child + visited.add(child) + if depth_now < depth_limit: + stack.append((child, get_children(child))) + depth_now += 1 + break + else: + stack.pop() + depth_now -= 1 + + +@nx._dispatchable(returns_graph=True) +def dfs_tree(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Returns oriented tree constructed from a depth-first-search from source. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + T : NetworkX DiGraph + An oriented tree + + Examples + -------- + >>> G = nx.path_graph(5) + >>> T = nx.dfs_tree(G, source=0, depth_limit=2) + >>> list(T.edges()) + [(0, 1), (1, 2)] + >>> T = nx.dfs_tree(G, source=0) + >>> list(T.edges()) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + See Also + -------- + dfs_preorder_nodes + dfs_postorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs` + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree` + """ + T = nx.DiGraph() + if source is None: + T.add_nodes_from(G) + else: + T.add_node(source) + T.add_edges_from(dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors)) + return T + + +@nx._dispatchable +def dfs_predecessors(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Returns dictionary of predecessors in depth-first-search from source. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search. + Note that you will get predecessors for all nodes in the + component containing `source`. This input only specifies + where the DFS starts. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + pred: dict + A dictionary with nodes as keys and predecessor nodes as values. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> nx.dfs_predecessors(G, source=0) + {1: 0, 2: 1, 3: 2} + >>> nx.dfs_predecessors(G, source=0, depth_limit=2) + {1: 0, 2: 1} + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in `PADS`_, with modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS + .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_preorder_nodes + dfs_postorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs` + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree` + """ + return { + t: s + for s, t in dfs_edges(G, source, depth_limit, sort_neighbors=sort_neighbors) + } + + +@nx._dispatchable +def dfs_successors(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Returns dictionary of successors in depth-first-search from source. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search. + Note that you will get successors for all nodes in the + component containing `source`. This input only specifies + where the DFS starts. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + succ: dict + A dictionary with nodes as keys and list of successor nodes as values. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> nx.dfs_successors(G, source=0) + {0: [1], 1: [2], 2: [3], 3: [4]} + >>> nx.dfs_successors(G, source=0, depth_limit=2) + {0: [1], 1: [2]} + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in `PADS`_, with modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS + .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_preorder_nodes + dfs_postorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs` + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree` + """ + d = defaultdict(list) + for s, t in dfs_edges( + G, + source=source, + depth_limit=depth_limit, + sort_neighbors=sort_neighbors, + ): + d[s].append(t) + return dict(d) + + +@nx._dispatchable +def dfs_postorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Generate nodes in a depth-first-search post-ordering starting at source. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + nodes: generator + A generator of nodes in a depth-first-search post-ordering. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> list(nx.dfs_postorder_nodes(G, source=0)) + [4, 3, 2, 1, 0] + >>> list(nx.dfs_postorder_nodes(G, source=0, depth_limit=2)) + [1, 0] + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in `PADS`_, with modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS + .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_edges + dfs_preorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.edgedfs.edge_dfs` + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_tree` + """ + edges = nx.dfs_labeled_edges( + G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors + ) + return (v for u, v, d in edges if d == "reverse") + + +@nx._dispatchable +def dfs_preorder_nodes(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Generate nodes in a depth-first-search pre-ordering starting at source. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search and return nodes in + the component reachable from source. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + nodes: generator + A generator of nodes in a depth-first-search pre-ordering. + + Examples + -------- + >>> G = nx.path_graph(5) + >>> list(nx.dfs_preorder_nodes(G, source=0)) + [0, 1, 2, 3, 4] + >>> list(nx.dfs_preorder_nodes(G, source=0, depth_limit=2)) + [0, 1, 2] + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in `PADS`_, with modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS + .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_edges + dfs_postorder_nodes + dfs_labeled_edges + :func:`~networkx.algorithms.traversal.breadth_first_search.bfs_edges` + """ + edges = nx.dfs_labeled_edges( + G, source=source, depth_limit=depth_limit, sort_neighbors=sort_neighbors + ) + return (v for u, v, d in edges if d == "forward") + + +@nx._dispatchable +def dfs_labeled_edges(G, source=None, depth_limit=None, *, sort_neighbors=None): + """Iterate over edges in a depth-first-search (DFS) labeled by type. + + Parameters + ---------- + G : NetworkX graph + + source : node, optional + Specify starting node for depth-first search and return edges in + the component reachable from source. + + depth_limit : int, optional (default=len(G)) + Specify the maximum search depth. + + sort_neighbors : function (default=None) + A function that takes an iterator over nodes as the input, and + returns an iterable of the same nodes with a custom ordering. + For example, `sorted` will sort the nodes in increasing order. + + Returns + ------- + edges: generator + A generator of triples of the form (*u*, *v*, *d*), where (*u*, + *v*) is the edge being explored in the depth-first search and *d* + is one of the strings 'forward', 'nontree', 'reverse', or 'reverse-depth_limit'. + A 'forward' edge is one in which *u* has been visited but *v* has + not. A 'nontree' edge is one in which both *u* and *v* have been + visited but the edge is not in the DFS tree. A 'reverse' edge is + one in which both *u* and *v* have been visited and the edge is in + the DFS tree. When the `depth_limit` is reached via a 'forward' edge, + a 'reverse' edge is immediately generated rather than the subtree + being explored. To indicate this flavor of 'reverse' edge, the string + yielded is 'reverse-depth_limit'. + + Examples + -------- + + The labels reveal the complete transcript of the depth-first search + algorithm in more detail than, for example, :func:`dfs_edges`:: + + >>> from pprint import pprint + >>> + >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 1)]) + >>> pprint(list(nx.dfs_labeled_edges(G, source=0))) + [(0, 0, 'forward'), + (0, 1, 'forward'), + (1, 2, 'forward'), + (2, 1, 'nontree'), + (1, 2, 'reverse'), + (0, 1, 'reverse'), + (0, 0, 'reverse')] + + Notes + ----- + If a source is not specified then a source is chosen arbitrarily and + repeatedly until all components in the graph are searched. + + The implementation of this function is adapted from David Eppstein's + depth-first search function in `PADS`_, with modifications + to allow depth limits based on the Wikipedia article + "`Depth-limited search`_". + + .. _PADS: http://www.ics.uci.edu/~eppstein/PADS + .. _Depth-limited search: https://en.wikipedia.org/wiki/Depth-limited_search + + See Also + -------- + dfs_edges + dfs_preorder_nodes + dfs_postorder_nodes + """ + # Based on http://www.ics.uci.edu/~eppstein/PADS/DFS.py + # by D. Eppstein, July 2004. + if source is None: + # edges for all components + nodes = G + else: + # edges for components with source + nodes = [source] + if depth_limit is None: + depth_limit = len(G) + + get_children = ( + G.neighbors + if sort_neighbors is None + else lambda n: iter(sort_neighbors(G.neighbors(n))) + ) + + visited = set() + for start in nodes: + if start in visited: + continue + yield start, start, "forward" + visited.add(start) + stack = [(start, get_children(start))] + depth_now = 1 + while stack: + parent, children = stack[-1] + for child in children: + if child in visited: + yield parent, child, "nontree" + else: + yield parent, child, "forward" + visited.add(child) + if depth_now < depth_limit: + stack.append((child, iter(get_children(child)))) + depth_now += 1 + break + else: + yield parent, child, "reverse-depth_limit" + else: + stack.pop() + depth_now -= 1 + if stack: + yield stack[-1][0], parent, "reverse" + yield start, start, "reverse" diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgebfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgebfs.py new file mode 100644 index 0000000000000000000000000000000000000000..2f468e10b3ebbf43888ac6761a473bbc2e1b106b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgebfs.py @@ -0,0 +1,185 @@ +""" +============================= +Breadth First Search on Edges +============================= + +Algorithms for a breadth-first traversal of edges in a graph. + +""" + +from collections import deque + +import networkx as nx + +FORWARD = "forward" +REVERSE = "reverse" + +__all__ = ["edge_bfs"] + + +@nx._dispatchable +def edge_bfs(G, source=None, orientation=None): + """A directed, breadth-first-search of edges in `G`, beginning at `source`. + + Yield the edges of G in a breadth-first-search order continuing until + all edges are generated. + + Parameters + ---------- + G : graph + A directed/undirected graph/multigraph. + + source : node, list of nodes + The node from which the traversal begins. If None, then a source + is chosen arbitrarily and repeatedly until all edges from each node in + the graph are searched. + + orientation : None | 'original' | 'reverse' | 'ignore' (default: None) + For directed graphs and directed multigraphs, edge traversals need not + respect the original orientation of the edges. + When set to 'reverse' every edge is traversed in the reverse direction. + When set to 'ignore', every edge is treated as undirected. + When set to 'original', every edge is treated as directed. + In all three cases, the yielded edge tuples add a last entry to + indicate the direction in which that edge was traversed. + If orientation is None, the yielded edge has no direction indicated. + The direction is respected, but not reported. + + Yields + ------ + edge : directed edge + A directed edge indicating the path taken by the breadth-first-search. + For graphs, `edge` is of the form `(u, v)` where `u` and `v` + are the tail and head of the edge as determined by the traversal. + For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is + the key of the edge. When the graph is directed, then `u` and `v` + are always in the order of the actual directed edge. + If orientation is not None then the edge tuple is extended to include + the direction of traversal ('forward' or 'reverse') on that edge. + + Examples + -------- + >>> from pprint import pprint + >>> nodes = [0, 1, 2, 3] + >>> edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)] + + >>> list(nx.edge_bfs(nx.Graph(edges), nodes)) + [(0, 1), (0, 2), (1, 2), (1, 3)] + + >>> list(nx.edge_bfs(nx.DiGraph(edges), nodes)) + [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)] + + >>> list(nx.edge_bfs(nx.MultiGraph(edges), nodes)) + [(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)] + + >>> list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes)) + [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)] + + >>> list(nx.edge_bfs(nx.DiGraph(edges), nodes, orientation="ignore")) + [(0, 1, 'forward'), (1, 0, 'reverse'), (2, 0, 'reverse'), (2, 1, 'reverse'), (3, 1, 'reverse')] + + >>> elist = list(nx.edge_bfs(nx.MultiDiGraph(edges), nodes, orientation="ignore")) + >>> pprint(elist) + [(0, 1, 0, 'forward'), + (1, 0, 0, 'reverse'), + (1, 0, 1, 'reverse'), + (2, 0, 0, 'reverse'), + (2, 1, 0, 'reverse'), + (3, 1, 0, 'reverse')] + + Notes + ----- + The goal of this function is to visit edges. It differs from the more + familiar breadth-first-search of nodes, as provided by + :func:`networkx.algorithms.traversal.breadth_first_search.bfs_edges`, in + that it does not stop once every node has been visited. In a directed graph + with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited + if not for the functionality provided by this function. + + The naming of this function is very similar to bfs_edges. The difference + is that 'edge_bfs' yields edges even if they extend back to an already + explored node while 'bfs_edges' yields the edges of the tree that results + from a breadth-first-search (BFS) so no edges are reported if they extend + to already explored nodes. That means 'edge_bfs' reports all edges while + 'bfs_edges' only report those traversed by a node-based BFS. Yet another + description is that 'bfs_edges' reports the edges traversed during BFS + while 'edge_bfs' reports all edges in the order they are explored. + + See Also + -------- + bfs_edges + bfs_tree + edge_dfs + + """ + nodes = list(G.nbunch_iter(source)) + if not nodes: + return + + directed = G.is_directed() + kwds = {"data": False} + if G.is_multigraph() is True: + kwds["keys"] = True + + # set up edge lookup + if orientation is None: + + def edges_from(node): + return iter(G.edges(node, **kwds)) + + elif not directed or orientation == "original": + + def edges_from(node): + for e in G.edges(node, **kwds): + yield e + (FORWARD,) + + elif orientation == "reverse": + + def edges_from(node): + for e in G.in_edges(node, **kwds): + yield e + (REVERSE,) + + elif orientation == "ignore": + + def edges_from(node): + for e in G.edges(node, **kwds): + yield e + (FORWARD,) + for e in G.in_edges(node, **kwds): + yield e + (REVERSE,) + + else: + raise nx.NetworkXError("invalid orientation argument.") + + if directed: + neighbors = G.successors + + def edge_id(edge): + # remove direction indicator + return edge[:-1] if orientation is not None else edge + + else: + neighbors = G.neighbors + + def edge_id(edge): + return (frozenset(edge[:2]),) + edge[2:] + + check_reverse = directed and orientation in ("reverse", "ignore") + + # start BFS + visited_nodes = set(nodes) + visited_edges = set() + queue = deque([(n, edges_from(n)) for n in nodes]) + while queue: + parent, children_edges = queue.popleft() + for edge in children_edges: + if check_reverse and edge[-1] == REVERSE: + child = edge[0] + else: + child = edge[1] + if child not in visited_nodes: + visited_nodes.add(child) + queue.append((child, edges_from(child))) + edgeid = edge_id(edge) + if edgeid not in visited_edges: + visited_edges.add(edgeid) + yield edge diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgedfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgedfs.py new file mode 100644 index 0000000000000000000000000000000000000000..f8173105ddb380f42accc31af87e4190b6af6ce1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/edgedfs.py @@ -0,0 +1,182 @@ +""" +=========================== +Depth First Search on Edges +=========================== + +Algorithms for a depth-first traversal of edges in a graph. + +""" + +import networkx as nx + +FORWARD = "forward" +REVERSE = "reverse" + +__all__ = ["edge_dfs"] + + +@nx._dispatchable +def edge_dfs(G, source=None, orientation=None): + """A directed, depth-first-search of edges in `G`, beginning at `source`. + + Yield the edges of G in a depth-first-search order continuing until + all edges are generated. + + Parameters + ---------- + G : graph + A directed/undirected graph/multigraph. + + source : node, list of nodes + The node from which the traversal begins. If None, then a source + is chosen arbitrarily and repeatedly until all edges from each node in + the graph are searched. + + orientation : None | 'original' | 'reverse' | 'ignore' (default: None) + For directed graphs and directed multigraphs, edge traversals need not + respect the original orientation of the edges. + When set to 'reverse' every edge is traversed in the reverse direction. + When set to 'ignore', every edge is treated as undirected. + When set to 'original', every edge is treated as directed. + In all three cases, the yielded edge tuples add a last entry to + indicate the direction in which that edge was traversed. + If orientation is None, the yielded edge has no direction indicated. + The direction is respected, but not reported. + + Yields + ------ + edge : directed edge + A directed edge indicating the path taken by the depth-first traversal. + For graphs, `edge` is of the form `(u, v)` where `u` and `v` + are the tail and head of the edge as determined by the traversal. + For multigraphs, `edge` is of the form `(u, v, key)`, where `key` is + the key of the edge. When the graph is directed, then `u` and `v` + are always in the order of the actual directed edge. + If orientation is not None then the edge tuple is extended to include + the direction of traversal ('forward' or 'reverse') on that edge. + + Examples + -------- + >>> from pprint import pprint + >>> nodes = [0, 1, 2, 3] + >>> edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)] + + >>> list(nx.edge_dfs(nx.Graph(edges), nodes)) + [(0, 1), (1, 2), (1, 3)] + + >>> list(nx.edge_dfs(nx.DiGraph(edges), nodes)) + [(0, 1), (1, 0), (2, 1), (3, 1)] + + >>> list(nx.edge_dfs(nx.MultiGraph(edges), nodes)) + [(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)] + + >>> list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes)) + [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)] + + >>> list(nx.edge_dfs(nx.DiGraph(edges), nodes, orientation="ignore")) + [(0, 1, 'forward'), (1, 0, 'forward'), (2, 1, 'reverse'), (3, 1, 'reverse')] + + >>> elist = list(nx.edge_dfs(nx.MultiDiGraph(edges), nodes, orientation="ignore")) + >>> pprint(elist) + [(0, 1, 0, 'forward'), + (1, 0, 0, 'forward'), + (1, 0, 1, 'reverse'), + (2, 1, 0, 'reverse'), + (3, 1, 0, 'reverse')] + + Notes + ----- + The goal of this function is to visit edges. It differs from the more + familiar depth-first traversal of nodes, as provided by + :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges`, in + that it does not stop once every node has been visited. In a directed graph + with edges [(0, 1), (1, 2), (2, 1)], the edge (2, 1) would not be visited + if not for the functionality provided by this function. + + See Also + -------- + :func:`~networkx.algorithms.traversal.depth_first_search.dfs_edges` + + """ + nodes = list(G.nbunch_iter(source)) + if not nodes: + return + + directed = G.is_directed() + kwds = {"data": False} + if G.is_multigraph() is True: + kwds["keys"] = True + + # set up edge lookup + if orientation is None: + + def edges_from(node): + return iter(G.edges(node, **kwds)) + + elif not directed or orientation == "original": + + def edges_from(node): + for e in G.edges(node, **kwds): + yield e + (FORWARD,) + + elif orientation == "reverse": + + def edges_from(node): + for e in G.in_edges(node, **kwds): + yield e + (REVERSE,) + + elif orientation == "ignore": + + def edges_from(node): + for e in G.edges(node, **kwds): + yield e + (FORWARD,) + for e in G.in_edges(node, **kwds): + yield e + (REVERSE,) + + else: + raise nx.NetworkXError("invalid orientation argument.") + + # set up formation of edge_id to easily look up if edge already returned + if directed: + + def edge_id(edge): + # remove direction indicator + return edge[:-1] if orientation is not None else edge + + else: + + def edge_id(edge): + # single id for undirected requires frozenset on nodes + return (frozenset(edge[:2]),) + edge[2:] + + # Basic setup + check_reverse = directed and orientation in ("reverse", "ignore") + + visited_edges = set() + visited_nodes = set() + edges = {} + + # start DFS + for start_node in nodes: + stack = [start_node] + while stack: + current_node = stack[-1] + if current_node not in visited_nodes: + edges[current_node] = edges_from(current_node) + visited_nodes.add(current_node) + + try: + edge = next(edges[current_node]) + except StopIteration: + # No more edges from the current node. + stack.pop() + else: + edgeid = edge_id(edge) + if edgeid not in visited_edges: + visited_edges.add(edgeid) + # Mark the traversed "to" node as to-be-explored. + if check_reverse and edge[-1] == REVERSE: + stack.append(edge[0]) + else: + stack.append(edge[1]) + yield edge diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8de49a6d6996d21bed8b2242753bf52925795ea5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..473e1b4f012d6bcd28234abcd183367c8a30992e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_beamsearch.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13ef12f67362fb46581801f724781af0320b0f56 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_bfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..791ac8ec64be06955d125120829ace9e93176e5a Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_dfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3d60e46ed74f67fdca982132b358c95a65f2f30 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgebfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5891ae91cd58d4b7cbb0b0bc4ab063493563bd86 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/__pycache__/test_edgedfs.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py new file mode 100644 index 0000000000000000000000000000000000000000..049f116b62fb595977d5ed94d01b9c15baf17fb3 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_beamsearch.py @@ -0,0 +1,25 @@ +"""Unit tests for the beam search functions.""" + +import pytest + +import networkx as nx + + +def test_narrow(): + """Tests that a narrow beam width may cause an incomplete search.""" + # In this search, we enqueue only the neighbor 3 at the first + # step, then only the neighbor 2 at the second step. Once at + # node 2, the search chooses node 3, since it has a higher value + # than node 1, but node 3 has already been visited, so the + # search terminates. + G = nx.cycle_graph(4) + edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=1) + assert list(edges) == [(0, 3), (3, 2)] + + +@pytest.mark.parametrize("width", (2, None)) +def test_wide(width): + """All nodes are searched when `width` is None or >= max degree""" + G = nx.cycle_graph(4) + edges = nx.bfs_beam_edges(G, source=0, value=lambda n: n, width=width) + assert list(edges) == [(0, 3), (0, 1), (3, 2)] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_bfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_bfs.py new file mode 100644 index 0000000000000000000000000000000000000000..7f4f37a31c4d6a780f084047b492ad52b8d04fae --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_bfs.py @@ -0,0 +1,203 @@ +from functools import partial + +import pytest + +import networkx as nx + + +class TestBFS: + @classmethod + def setup_class(cls): + # simple graph + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) + cls.G = G + + def test_successor(self): + assert dict(nx.bfs_successors(self.G, source=0)) == {0: [1], 1: [2, 3], 2: [4]} + + def test_predecessor(self): + assert dict(nx.bfs_predecessors(self.G, source=0)) == {1: 0, 2: 1, 3: 1, 4: 2} + + def test_bfs_tree(self): + T = nx.bfs_tree(self.G, source=0) + assert sorted(T.nodes()) == sorted(self.G.nodes()) + assert sorted(T.edges()) == [(0, 1), (1, 2), (1, 3), (2, 4)] + + def test_bfs_edges(self): + edges = nx.bfs_edges(self.G, source=0) + assert list(edges) == [(0, 1), (1, 2), (1, 3), (2, 4)] + + def test_bfs_edges_reverse(self): + D = nx.DiGraph() + D.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 4)]) + edges = nx.bfs_edges(D, source=4, reverse=True) + assert list(edges) == [(4, 2), (4, 3), (2, 1), (1, 0)] + + def test_bfs_edges_sorting(self): + D = nx.DiGraph() + D.add_edges_from([(0, 1), (0, 2), (1, 4), (1, 3), (2, 5)]) + sort_desc = partial(sorted, reverse=True) + edges_asc = nx.bfs_edges(D, source=0, sort_neighbors=sorted) + edges_desc = nx.bfs_edges(D, source=0, sort_neighbors=sort_desc) + assert list(edges_asc) == [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5)] + assert list(edges_desc) == [(0, 2), (0, 1), (2, 5), (1, 4), (1, 3)] + + def test_bfs_tree_isolates(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + T = nx.bfs_tree(G, source=1) + assert sorted(T.nodes()) == [1] + assert sorted(T.edges()) == [] + + def test_bfs_layers(self): + expected = { + 0: [0], + 1: [1], + 2: [2, 3], + 3: [4], + } + for sources in [0, [0], (i for i in [0]), [0, 0]]: + assert dict(enumerate(nx.bfs_layers(self.G, sources))) == expected + + def test_bfs_layers_missing_source(self): + with pytest.raises(nx.NetworkXError): + next(nx.bfs_layers(self.G, sources="abc")) + with pytest.raises(nx.NetworkXError): + next(nx.bfs_layers(self.G, sources=["abc"])) + + def test_descendants_at_distance(self): + for distance, descendants in enumerate([{0}, {1}, {2, 3}, {4}]): + assert nx.descendants_at_distance(self.G, 0, distance) == descendants + + def test_descendants_at_distance_missing_source(self): + with pytest.raises(nx.NetworkXError): + nx.descendants_at_distance(self.G, "abc", 0) + + def test_bfs_labeled_edges_directed(self): + D = nx.cycle_graph(5, create_using=nx.DiGraph) + expected = [ + (0, 1, "tree"), + (1, 2, "tree"), + (2, 3, "tree"), + (3, 4, "tree"), + (4, 0, "reverse"), + ] + answer = list(nx.bfs_labeled_edges(D, 0)) + assert expected == answer + + D.add_edge(4, 4) + expected.append((4, 4, "level")) + answer = list(nx.bfs_labeled_edges(D, 0)) + assert expected == answer + + D.add_edge(0, 2) + D.add_edge(1, 5) + D.add_edge(2, 5) + D.remove_edge(4, 4) + expected = [ + (0, 1, "tree"), + (0, 2, "tree"), + (1, 2, "level"), + (1, 5, "tree"), + (2, 3, "tree"), + (2, 5, "forward"), + (3, 4, "tree"), + (4, 0, "reverse"), + ] + answer = list(nx.bfs_labeled_edges(D, 0)) + assert expected == answer + + G = D.to_undirected() + G.add_edge(4, 4) + expected = [ + (0, 1, "tree"), + (0, 2, "tree"), + (0, 4, "tree"), + (1, 2, "level"), + (1, 5, "tree"), + (2, 3, "tree"), + (2, 5, "forward"), + (4, 3, "forward"), + (4, 4, "level"), + ] + answer = list(nx.bfs_labeled_edges(G, 0)) + assert expected == answer + + +class TestBreadthLimitedSearch: + @classmethod + def setup_class(cls): + # a tree + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3, 4, 5, 6]) + nx.add_path(G, [2, 7, 8, 9, 10]) + cls.G = G + # a disconnected graph + D = nx.Graph() + D.add_edges_from([(0, 1), (2, 3)]) + nx.add_path(D, [2, 7, 8, 9, 10]) + cls.D = D + + def test_limited_bfs_successor(self): + assert dict(nx.bfs_successors(self.G, source=1, depth_limit=3)) == { + 1: [0, 2], + 2: [3, 7], + 3: [4], + 7: [8], + } + result = { + n: sorted(s) for n, s in nx.bfs_successors(self.D, source=7, depth_limit=2) + } + assert result == {8: [9], 2: [3], 7: [2, 8]} + + def test_limited_bfs_predecessor(self): + assert dict(nx.bfs_predecessors(self.G, source=1, depth_limit=3)) == { + 0: 1, + 2: 1, + 3: 2, + 4: 3, + 7: 2, + 8: 7, + } + assert dict(nx.bfs_predecessors(self.D, source=7, depth_limit=2)) == { + 2: 7, + 3: 2, + 8: 7, + 9: 8, + } + + def test_limited_bfs_tree(self): + T = nx.bfs_tree(self.G, source=3, depth_limit=1) + assert sorted(T.edges()) == [(3, 2), (3, 4)] + + def test_limited_bfs_edges(self): + edges = nx.bfs_edges(self.G, source=9, depth_limit=4) + assert list(edges) == [(9, 8), (9, 10), (8, 7), (7, 2), (2, 1), (2, 3)] + + def test_limited_bfs_layers(self): + assert dict(enumerate(nx.bfs_layers(self.G, sources=[0]))) == { + 0: [0], + 1: [1], + 2: [2], + 3: [3, 7], + 4: [4, 8], + 5: [5, 9], + 6: [6, 10], + } + assert dict(enumerate(nx.bfs_layers(self.D, sources=2))) == { + 0: [2], + 1: [3, 7], + 2: [8], + 3: [9], + 4: [10], + } + + def test_limited_descendants_at_distance(self): + for distance, descendants in enumerate( + [{0}, {1}, {2}, {3, 7}, {4, 8}, {5, 9}, {6, 10}] + ): + assert nx.descendants_at_distance(self.G, 0, distance) == descendants + for distance, descendants in enumerate([{2}, {3, 7}, {8}, {9}, {10}]): + assert nx.descendants_at_distance(self.D, 2, distance) == descendants diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_dfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_dfs.py new file mode 100644 index 0000000000000000000000000000000000000000..292de05a083861d6cca44bbaa72ea422358d376e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_dfs.py @@ -0,0 +1,307 @@ +import networkx as nx + + +class TestDFS: + @classmethod + def setup_class(cls): + # simple graph + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (1, 3), (2, 4), (3, 0), (0, 4)]) + cls.G = G + # simple graph, disconnected + D = nx.Graph() + D.add_edges_from([(0, 1), (2, 3)]) + cls.D = D + + def test_preorder_nodes(self): + assert list(nx.dfs_preorder_nodes(self.G, source=0)) == [0, 1, 2, 4, 3] + assert list(nx.dfs_preorder_nodes(self.D)) == [0, 1, 2, 3] + assert list(nx.dfs_preorder_nodes(self.D, source=2)) == [2, 3] + + def test_postorder_nodes(self): + assert list(nx.dfs_postorder_nodes(self.G, source=0)) == [4, 2, 3, 1, 0] + assert list(nx.dfs_postorder_nodes(self.D)) == [1, 0, 3, 2] + assert list(nx.dfs_postorder_nodes(self.D, source=0)) == [1, 0] + + def test_successor(self): + assert nx.dfs_successors(self.G, source=0) == {0: [1], 1: [2, 3], 2: [4]} + assert nx.dfs_successors(self.G, source=1) == {0: [3, 4], 1: [0], 4: [2]} + assert nx.dfs_successors(self.D) == {0: [1], 2: [3]} + assert nx.dfs_successors(self.D, source=1) == {1: [0]} + + def test_predecessor(self): + assert nx.dfs_predecessors(self.G, source=0) == {1: 0, 2: 1, 3: 1, 4: 2} + assert nx.dfs_predecessors(self.D) == {1: 0, 3: 2} + + def test_dfs_tree(self): + exp_nodes = sorted(self.G.nodes()) + exp_edges = [(0, 1), (1, 2), (1, 3), (2, 4)] + # Search from first node + T = nx.dfs_tree(self.G, source=0) + assert sorted(T.nodes()) == exp_nodes + assert sorted(T.edges()) == exp_edges + # Check source=None + T = nx.dfs_tree(self.G, source=None) + assert sorted(T.nodes()) == exp_nodes + assert sorted(T.edges()) == exp_edges + # Check source=None is the default + T = nx.dfs_tree(self.G) + assert sorted(T.nodes()) == exp_nodes + assert sorted(T.edges()) == exp_edges + + def test_dfs_edges(self): + edges = nx.dfs_edges(self.G, source=0) + assert list(edges) == [(0, 1), (1, 2), (2, 4), (1, 3)] + edges = nx.dfs_edges(self.D) + assert list(edges) == [(0, 1), (2, 3)] + + def test_dfs_edges_sorting(self): + G = nx.Graph([(0, 1), (1, 2), (1, 3), (2, 4), (3, 0), (0, 4)]) + edges_asc = nx.dfs_edges(G, source=0, sort_neighbors=sorted) + edges_desc = nx.dfs_edges( + G, source=0, sort_neighbors=lambda x: sorted(x, reverse=True) + ) + assert list(edges_asc) == [(0, 1), (1, 2), (2, 4), (1, 3)] + assert list(edges_desc) == [(0, 4), (4, 2), (2, 1), (1, 3)] + + def test_dfs_labeled_edges(self): + edges = list(nx.dfs_labeled_edges(self.G, source=0)) + forward = [(u, v) for (u, v, d) in edges if d == "forward"] + assert forward == [(0, 0), (0, 1), (1, 2), (2, 4), (1, 3)] + assert edges == [ + (0, 0, "forward"), + (0, 1, "forward"), + (1, 0, "nontree"), + (1, 2, "forward"), + (2, 1, "nontree"), + (2, 4, "forward"), + (4, 2, "nontree"), + (4, 0, "nontree"), + (2, 4, "reverse"), + (1, 2, "reverse"), + (1, 3, "forward"), + (3, 1, "nontree"), + (3, 0, "nontree"), + (1, 3, "reverse"), + (0, 1, "reverse"), + (0, 3, "nontree"), + (0, 4, "nontree"), + (0, 0, "reverse"), + ] + + def test_dfs_labeled_edges_sorting(self): + G = nx.Graph([(0, 1), (1, 2), (1, 3), (2, 4), (3, 0), (0, 4)]) + edges_asc = nx.dfs_labeled_edges(G, source=0, sort_neighbors=sorted) + edges_desc = nx.dfs_labeled_edges( + G, source=0, sort_neighbors=lambda x: sorted(x, reverse=True) + ) + assert list(edges_asc) == [ + (0, 0, "forward"), + (0, 1, "forward"), + (1, 0, "nontree"), + (1, 2, "forward"), + (2, 1, "nontree"), + (2, 4, "forward"), + (4, 0, "nontree"), + (4, 2, "nontree"), + (2, 4, "reverse"), + (1, 2, "reverse"), + (1, 3, "forward"), + (3, 0, "nontree"), + (3, 1, "nontree"), + (1, 3, "reverse"), + (0, 1, "reverse"), + (0, 3, "nontree"), + (0, 4, "nontree"), + (0, 0, "reverse"), + ] + assert list(edges_desc) == [ + (0, 0, "forward"), + (0, 4, "forward"), + (4, 2, "forward"), + (2, 4, "nontree"), + (2, 1, "forward"), + (1, 3, "forward"), + (3, 1, "nontree"), + (3, 0, "nontree"), + (1, 3, "reverse"), + (1, 2, "nontree"), + (1, 0, "nontree"), + (2, 1, "reverse"), + (4, 2, "reverse"), + (4, 0, "nontree"), + (0, 4, "reverse"), + (0, 3, "nontree"), + (0, 1, "nontree"), + (0, 0, "reverse"), + ] + + def test_dfs_labeled_disconnected_edges(self): + edges = list(nx.dfs_labeled_edges(self.D)) + forward = [(u, v) for (u, v, d) in edges if d == "forward"] + assert forward == [(0, 0), (0, 1), (2, 2), (2, 3)] + assert edges == [ + (0, 0, "forward"), + (0, 1, "forward"), + (1, 0, "nontree"), + (0, 1, "reverse"), + (0, 0, "reverse"), + (2, 2, "forward"), + (2, 3, "forward"), + (3, 2, "nontree"), + (2, 3, "reverse"), + (2, 2, "reverse"), + ] + + def test_dfs_tree_isolates(self): + G = nx.Graph() + G.add_node(1) + G.add_node(2) + T = nx.dfs_tree(G, source=1) + assert sorted(T.nodes()) == [1] + assert sorted(T.edges()) == [] + T = nx.dfs_tree(G, source=None) + assert sorted(T.nodes()) == [1, 2] + assert sorted(T.edges()) == [] + + +class TestDepthLimitedSearch: + @classmethod + def setup_class(cls): + # a tree + G = nx.Graph() + nx.add_path(G, [0, 1, 2, 3, 4, 5, 6]) + nx.add_path(G, [2, 7, 8, 9, 10]) + cls.G = G + # a disconnected graph + D = nx.Graph() + D.add_edges_from([(0, 1), (2, 3)]) + nx.add_path(D, [2, 7, 8, 9, 10]) + cls.D = D + + def test_dls_preorder_nodes(self): + assert list(nx.dfs_preorder_nodes(self.G, source=0, depth_limit=2)) == [0, 1, 2] + assert list(nx.dfs_preorder_nodes(self.D, source=1, depth_limit=2)) == ([1, 0]) + + def test_dls_postorder_nodes(self): + assert list(nx.dfs_postorder_nodes(self.G, source=3, depth_limit=3)) == [ + 1, + 7, + 2, + 5, + 4, + 3, + ] + assert list(nx.dfs_postorder_nodes(self.D, source=2, depth_limit=2)) == ( + [3, 7, 2] + ) + + def test_dls_successor(self): + result = nx.dfs_successors(self.G, source=4, depth_limit=3) + assert {n: set(v) for n, v in result.items()} == { + 2: {1, 7}, + 3: {2}, + 4: {3, 5}, + 5: {6}, + } + result = nx.dfs_successors(self.D, source=7, depth_limit=2) + assert {n: set(v) for n, v in result.items()} == {8: {9}, 2: {3}, 7: {8, 2}} + + def test_dls_predecessor(self): + assert nx.dfs_predecessors(self.G, source=0, depth_limit=3) == { + 1: 0, + 2: 1, + 3: 2, + 7: 2, + } + assert nx.dfs_predecessors(self.D, source=2, depth_limit=3) == { + 8: 7, + 9: 8, + 3: 2, + 7: 2, + } + + def test_dls_tree(self): + T = nx.dfs_tree(self.G, source=3, depth_limit=1) + assert sorted(T.edges()) == [(3, 2), (3, 4)] + + def test_dls_edges(self): + edges = nx.dfs_edges(self.G, source=9, depth_limit=4) + assert list(edges) == [(9, 8), (8, 7), (7, 2), (2, 1), (2, 3), (9, 10)] + + def test_dls_labeled_edges_depth_1(self): + edges = list(nx.dfs_labeled_edges(self.G, source=5, depth_limit=1)) + forward = [(u, v) for (u, v, d) in edges if d == "forward"] + assert forward == [(5, 5), (5, 4), (5, 6)] + # Note: reverse-depth_limit edge types were not reported before gh-6240 + assert edges == [ + (5, 5, "forward"), + (5, 4, "forward"), + (5, 4, "reverse-depth_limit"), + (5, 6, "forward"), + (5, 6, "reverse-depth_limit"), + (5, 5, "reverse"), + ] + + def test_dls_labeled_edges_depth_2(self): + edges = list(nx.dfs_labeled_edges(self.G, source=6, depth_limit=2)) + forward = [(u, v) for (u, v, d) in edges if d == "forward"] + assert forward == [(6, 6), (6, 5), (5, 4)] + assert edges == [ + (6, 6, "forward"), + (6, 5, "forward"), + (5, 4, "forward"), + (5, 4, "reverse-depth_limit"), + (5, 6, "nontree"), + (6, 5, "reverse"), + (6, 6, "reverse"), + ] + + def test_dls_labeled_disconnected_edges(self): + edges = list(nx.dfs_labeled_edges(self.D, depth_limit=1)) + assert edges == [ + (0, 0, "forward"), + (0, 1, "forward"), + (0, 1, "reverse-depth_limit"), + (0, 0, "reverse"), + (2, 2, "forward"), + (2, 3, "forward"), + (2, 3, "reverse-depth_limit"), + (2, 7, "forward"), + (2, 7, "reverse-depth_limit"), + (2, 2, "reverse"), + (8, 8, "forward"), + (8, 7, "nontree"), + (8, 9, "forward"), + (8, 9, "reverse-depth_limit"), + (8, 8, "reverse"), + (10, 10, "forward"), + (10, 9, "nontree"), + (10, 10, "reverse"), + ] + # large depth_limit has no impact + edges = list(nx.dfs_labeled_edges(self.D, depth_limit=19)) + assert edges == [ + (0, 0, "forward"), + (0, 1, "forward"), + (1, 0, "nontree"), + (0, 1, "reverse"), + (0, 0, "reverse"), + (2, 2, "forward"), + (2, 3, "forward"), + (3, 2, "nontree"), + (2, 3, "reverse"), + (2, 7, "forward"), + (7, 2, "nontree"), + (7, 8, "forward"), + (8, 7, "nontree"), + (8, 9, "forward"), + (9, 8, "nontree"), + (9, 10, "forward"), + (10, 9, "nontree"), + (9, 10, "reverse"), + (8, 9, "reverse"), + (7, 8, "reverse"), + (2, 7, "reverse"), + (2, 2, "reverse"), + ] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgebfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgebfs.py new file mode 100644 index 0000000000000000000000000000000000000000..1bf3fae0bd067dd548281e3382a6125f6e50ee22 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgebfs.py @@ -0,0 +1,147 @@ +import pytest + +import networkx as nx +from networkx.algorithms.traversal.edgedfs import FORWARD, REVERSE + + +class TestEdgeBFS: + @classmethod + def setup_class(cls): + cls.nodes = [0, 1, 2, 3] + cls.edges = [(0, 1), (1, 0), (1, 0), (2, 0), (2, 1), (3, 1)] + + def test_empty(self): + G = nx.Graph() + edges = list(nx.edge_bfs(G)) + assert edges == [] + + def test_graph_single_source(self): + G = nx.Graph(self.edges) + G.add_edge(4, 5) + x = list(nx.edge_bfs(G, [0])) + x_ = [(0, 1), (0, 2), (1, 2), (1, 3)] + assert x == x_ + + def test_graph(self): + G = nx.Graph(self.edges) + x = list(nx.edge_bfs(G, self.nodes)) + x_ = [(0, 1), (0, 2), (1, 2), (1, 3)] + assert x == x_ + + def test_digraph(self): + G = nx.DiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes)) + x_ = [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)] + assert x == x_ + + def test_digraph_orientation_invalid(self): + G = nx.DiGraph(self.edges) + edge_iterator = nx.edge_bfs(G, self.nodes, orientation="hello") + pytest.raises(nx.NetworkXError, list, edge_iterator) + + def test_digraph_orientation_none(self): + G = nx.DiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation=None)) + x_ = [(0, 1), (1, 0), (2, 0), (2, 1), (3, 1)] + assert x == x_ + + def test_digraph_orientation_original(self): + G = nx.DiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation="original")) + x_ = [ + (0, 1, FORWARD), + (1, 0, FORWARD), + (2, 0, FORWARD), + (2, 1, FORWARD), + (3, 1, FORWARD), + ] + assert x == x_ + + def test_digraph2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(nx.edge_bfs(G, [0])) + x_ = [(0, 1), (1, 2), (2, 3)] + assert x == x_ + + def test_digraph_rev(self): + G = nx.DiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation="reverse")) + x_ = [ + (1, 0, REVERSE), + (2, 0, REVERSE), + (0, 1, REVERSE), + (2, 1, REVERSE), + (3, 1, REVERSE), + ] + assert x == x_ + + def test_digraph_rev2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(nx.edge_bfs(G, [3], orientation="reverse")) + x_ = [(2, 3, REVERSE), (1, 2, REVERSE), (0, 1, REVERSE)] + assert x == x_ + + def test_multigraph(self): + G = nx.MultiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes)) + x_ = [(0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (1, 2, 0), (1, 3, 0)] + # This is an example of where hash randomization can break. + # There are 3! * 2 alternative outputs, such as: + # [(0, 1, 1), (1, 0, 0), (0, 1, 2), (1, 3, 0), (1, 2, 0)] + # But note, the edges (1,2,0) and (1,3,0) always follow the (0,1,k) + # edges. So the algorithm only guarantees a partial order. A total + # order is guaranteed only if the graph data structures are ordered. + assert x == x_ + + def test_multidigraph(self): + G = nx.MultiDiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes)) + x_ = [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 0, 0), (2, 1, 0), (3, 1, 0)] + assert x == x_ + + def test_multidigraph_rev(self): + G = nx.MultiDiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation="reverse")) + x_ = [ + (1, 0, 0, REVERSE), + (1, 0, 1, REVERSE), + (2, 0, 0, REVERSE), + (0, 1, 0, REVERSE), + (2, 1, 0, REVERSE), + (3, 1, 0, REVERSE), + ] + assert x == x_ + + def test_digraph_ignore(self): + G = nx.DiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation="ignore")) + x_ = [ + (0, 1, FORWARD), + (1, 0, REVERSE), + (2, 0, REVERSE), + (2, 1, REVERSE), + (3, 1, REVERSE), + ] + assert x == x_ + + def test_digraph_ignore2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(nx.edge_bfs(G, [0], orientation="ignore")) + x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 3, FORWARD)] + assert x == x_ + + def test_multidigraph_ignore(self): + G = nx.MultiDiGraph(self.edges) + x = list(nx.edge_bfs(G, self.nodes, orientation="ignore")) + x_ = [ + (0, 1, 0, FORWARD), + (1, 0, 0, REVERSE), + (1, 0, 1, REVERSE), + (2, 0, 0, REVERSE), + (2, 1, 0, REVERSE), + (3, 1, 0, REVERSE), + ] + assert x == x_ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgedfs.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgedfs.py new file mode 100644 index 0000000000000000000000000000000000000000..7c1967cce04b3a0c9db80f9af39d7b1dfd8ef4cb --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/traversal/tests/test_edgedfs.py @@ -0,0 +1,131 @@ +import pytest + +import networkx as nx +from networkx.algorithms import edge_dfs +from networkx.algorithms.traversal.edgedfs import FORWARD, REVERSE + +# These tests can fail with hash randomization. The easiest and clearest way +# to write these unit tests is for the edges to be output in an expected total +# order, but we cannot guarantee the order amongst outgoing edges from a node, +# unless each class uses an ordered data structure for neighbors. This is +# painful to do with the current API. The alternative is that the tests are +# written (IMO confusingly) so that there is not a total order over the edges, +# but only a partial order. Due to the small size of the graphs, hopefully +# failures due to hash randomization will not occur. For an example of how +# this can fail, see TestEdgeDFS.test_multigraph. + + +class TestEdgeDFS: + @classmethod + def setup_class(cls): + cls.nodes = [0, 1, 2, 3] + cls.edges = [(0, 1), (1, 0), (1, 0), (2, 1), (3, 1)] + + def test_empty(self): + G = nx.Graph() + edges = list(edge_dfs(G)) + assert edges == [] + + def test_graph(self): + G = nx.Graph(self.edges) + x = list(edge_dfs(G, self.nodes)) + x_ = [(0, 1), (1, 2), (1, 3)] + assert x == x_ + + def test_digraph(self): + G = nx.DiGraph(self.edges) + x = list(edge_dfs(G, self.nodes)) + x_ = [(0, 1), (1, 0), (2, 1), (3, 1)] + assert x == x_ + + def test_digraph_orientation_invalid(self): + G = nx.DiGraph(self.edges) + edge_iterator = edge_dfs(G, self.nodes, orientation="hello") + pytest.raises(nx.NetworkXError, list, edge_iterator) + + def test_digraph_orientation_none(self): + G = nx.DiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation=None)) + x_ = [(0, 1), (1, 0), (2, 1), (3, 1)] + assert x == x_ + + def test_digraph_orientation_original(self): + G = nx.DiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation="original")) + x_ = [(0, 1, FORWARD), (1, 0, FORWARD), (2, 1, FORWARD), (3, 1, FORWARD)] + assert x == x_ + + def test_digraph2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(edge_dfs(G, [0])) + x_ = [(0, 1), (1, 2), (2, 3)] + assert x == x_ + + def test_digraph_rev(self): + G = nx.DiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation="reverse")) + x_ = [(1, 0, REVERSE), (0, 1, REVERSE), (2, 1, REVERSE), (3, 1, REVERSE)] + assert x == x_ + + def test_digraph_rev2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(edge_dfs(G, [3], orientation="reverse")) + x_ = [(2, 3, REVERSE), (1, 2, REVERSE), (0, 1, REVERSE)] + assert x == x_ + + def test_multigraph(self): + G = nx.MultiGraph(self.edges) + x = list(edge_dfs(G, self.nodes)) + x_ = [(0, 1, 0), (1, 0, 1), (0, 1, 2), (1, 2, 0), (1, 3, 0)] + # This is an example of where hash randomization can break. + # There are 3! * 2 alternative outputs, such as: + # [(0, 1, 1), (1, 0, 0), (0, 1, 2), (1, 3, 0), (1, 2, 0)] + # But note, the edges (1,2,0) and (1,3,0) always follow the (0,1,k) + # edges. So the algorithm only guarantees a partial order. A total + # order is guaranteed only if the graph data structures are ordered. + assert x == x_ + + def test_multidigraph(self): + G = nx.MultiDiGraph(self.edges) + x = list(edge_dfs(G, self.nodes)) + x_ = [(0, 1, 0), (1, 0, 0), (1, 0, 1), (2, 1, 0), (3, 1, 0)] + assert x == x_ + + def test_multidigraph_rev(self): + G = nx.MultiDiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation="reverse")) + x_ = [ + (1, 0, 0, REVERSE), + (0, 1, 0, REVERSE), + (1, 0, 1, REVERSE), + (2, 1, 0, REVERSE), + (3, 1, 0, REVERSE), + ] + assert x == x_ + + def test_digraph_ignore(self): + G = nx.DiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation="ignore")) + x_ = [(0, 1, FORWARD), (1, 0, FORWARD), (2, 1, REVERSE), (3, 1, REVERSE)] + assert x == x_ + + def test_digraph_ignore2(self): + G = nx.DiGraph() + nx.add_path(G, range(4)) + x = list(edge_dfs(G, [0], orientation="ignore")) + x_ = [(0, 1, FORWARD), (1, 2, FORWARD), (2, 3, FORWARD)] + assert x == x_ + + def test_multidigraph_ignore(self): + G = nx.MultiDiGraph(self.edges) + x = list(edge_dfs(G, self.nodes, orientation="ignore")) + x_ = [ + (0, 1, 0, FORWARD), + (1, 0, 0, FORWARD), + (1, 0, 1, REVERSE), + (2, 1, 0, REVERSE), + (3, 1, 0, REVERSE), + ] + assert x == x_ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c6b6da30e2da0924e96987eb75de268f1b0278f0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__init__.py @@ -0,0 +1,7 @@ +from .branchings import * +from .coding import * +from .distance_measures import * +from .mst import * +from .recognition import * +from .operations import * +from .decomposition import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4edfa498dc7840c6fae3338ab5998d930e397c18 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/branchings.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/branchings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e250002db70dd0c9fe927f223c8a8d6635c87c7 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/branchings.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..711c5f95a0552cf7cab00d71c853278ad298d1d6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/coding.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f853b6a64c6fb17d22389593b2c3ef073bcb7848 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/decomposition.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..400064d5b5e8eba33511c72cd35f019f19fc40aa Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c2e48cac6bbdb9c0cf5704541226d65f2adc978 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/mst.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30d1080b81c42f7cad7f33880b65cf96b2b95667 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/operations.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b01a7947d3252254f2e7d1e0ed6b3fc69de350b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/__pycache__/recognition.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/branchings.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/branchings.py new file mode 100644 index 0000000000000000000000000000000000000000..cc9c7cf1189d341577a81501e5ca6760ed73a58c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/branchings.py @@ -0,0 +1,1042 @@ +""" +Algorithms for finding optimum branchings and spanning arborescences. + +This implementation is based on: + + J. Edmonds, Optimum branchings, J. Res. Natl. Bur. Standards 71B (1967), + 233–240. URL: http://archive.org/details/jresv71Bn4p233 + +""" + +# TODO: Implement method from Gabow, Galil, Spence and Tarjan: +# +# @article{ +# year={1986}, +# issn={0209-9683}, +# journal={Combinatorica}, +# volume={6}, +# number={2}, +# doi={10.1007/BF02579168}, +# title={Efficient algorithms for finding minimum spanning trees in +# undirected and directed graphs}, +# url={https://doi.org/10.1007/BF02579168}, +# publisher={Springer-Verlag}, +# keywords={68 B 15; 68 C 05}, +# author={Gabow, Harold N. and Galil, Zvi and Spencer, Thomas and Tarjan, +# Robert E.}, +# pages={109-122}, +# language={English} +# } +import string +from dataclasses import dataclass, field +from operator import itemgetter +from queue import PriorityQueue + +import networkx as nx +from networkx.utils import py_random_state + +from .recognition import is_arborescence, is_branching + +__all__ = [ + "branching_weight", + "greedy_branching", + "maximum_branching", + "minimum_branching", + "minimal_branching", + "maximum_spanning_arborescence", + "minimum_spanning_arborescence", + "ArborescenceIterator", +] + +KINDS = {"max", "min"} + +STYLES = { + "branching": "branching", + "arborescence": "arborescence", + "spanning arborescence": "arborescence", +} + +INF = float("inf") + + +@py_random_state(1) +def random_string(L=15, seed=None): + return "".join([seed.choice(string.ascii_letters) for n in range(L)]) + + +def _min_weight(weight): + return -weight + + +def _max_weight(weight): + return weight + + +@nx._dispatchable(edge_attrs={"attr": "default"}) +def branching_weight(G, attr="weight", default=1): + """ + Returns the total weight of a branching. + + You must access this function through the networkx.algorithms.tree module. + + Parameters + ---------- + G : DiGraph + The directed graph. + attr : str + The attribute to use as weights. If None, then each edge will be + treated equally with a weight of 1. + default : float + When `attr` is not None, then if an edge does not have that attribute, + `default` specifies what value it should take. + + Returns + ------- + weight: int or float + The total weight of the branching. + + Examples + -------- + >>> G = nx.DiGraph() + >>> G.add_weighted_edges_from([(0, 1, 2), (1, 2, 4), (2, 3, 3), (3, 4, 2)]) + >>> nx.tree.branching_weight(G) + 11 + + """ + return sum(edge[2].get(attr, default) for edge in G.edges(data=True)) + + +@py_random_state(4) +@nx._dispatchable(edge_attrs={"attr": "default"}, returns_graph=True) +def greedy_branching(G, attr="weight", default=1, kind="max", seed=None): + """ + Returns a branching obtained through a greedy algorithm. + + This algorithm is wrong, and cannot give a proper optimal branching. + However, we include it for pedagogical reasons, as it can be helpful to + see what its outputs are. + + The output is a branching, and possibly, a spanning arborescence. However, + it is not guaranteed to be optimal in either case. + + Parameters + ---------- + G : DiGraph + The directed graph to scan. + attr : str + The attribute to use as weights. If None, then each edge will be + treated equally with a weight of 1. + default : float + When `attr` is not None, then if an edge does not have that attribute, + `default` specifies what value it should take. + kind : str + The type of optimum to search for: 'min' or 'max' greedy branching. + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + B : directed graph + The greedily obtained branching. + + """ + if kind not in KINDS: + raise nx.NetworkXException("Unknown value for `kind`.") + + if kind == "min": + reverse = False + else: + reverse = True + + if attr is None: + # Generate a random string the graph probably won't have. + attr = random_string(seed=seed) + + edges = [(u, v, data.get(attr, default)) for (u, v, data) in G.edges(data=True)] + + # We sort by weight, but also by nodes to normalize behavior across runs. + try: + edges.sort(key=itemgetter(2, 0, 1), reverse=reverse) + except TypeError: + # This will fail in Python 3.x if the nodes are of varying types. + # In that case, we use the arbitrary order. + edges.sort(key=itemgetter(2), reverse=reverse) + + # The branching begins with a forest of no edges. + B = nx.DiGraph() + B.add_nodes_from(G) + + # Now we add edges greedily so long we maintain the branching. + uf = nx.utils.UnionFind() + for i, (u, v, w) in enumerate(edges): + if uf[u] == uf[v]: + # Adding this edge would form a directed cycle. + continue + elif B.in_degree(v) == 1: + # The edge would increase the degree to be greater than one. + continue + else: + # If attr was None, then don't insert weights... + data = {} + if attr is not None: + data[attr] = w + B.add_edge(u, v, **data) + uf.union(u, v) + + return B + + +@nx._dispatchable(preserve_edge_attrs=True, returns_graph=True) +def maximum_branching( + G, + attr="weight", + default=1, + preserve_attrs=False, + partition=None, +): + ####################################### + ### Data Structure Helper Functions ### + ####################################### + + def edmonds_add_edge(G, edge_index, u, v, key, **d): + """ + Adds an edge to `G` while also updating the edge index. + + This algorithm requires the use of an external dictionary to track + the edge keys since it is possible that the source or destination + node of an edge will be changed and the default key-handling + capabilities of the MultiDiGraph class do not account for this. + + Parameters + ---------- + G : MultiDiGraph + The graph to insert an edge into. + edge_index : dict + A mapping from integers to the edges of the graph. + u : node + The source node of the new edge. + v : node + The destination node of the new edge. + key : int + The key to use from `edge_index`. + d : keyword arguments, optional + Other attributes to store on the new edge. + """ + + if key in edge_index: + uu, vv, _ = edge_index[key] + if (u != uu) or (v != vv): + raise Exception(f"Key {key!r} is already in use.") + + G.add_edge(u, v, key, **d) + edge_index[key] = (u, v, G.succ[u][v][key]) + + def edmonds_remove_node(G, edge_index, n): + """ + Remove a node from the graph, updating the edge index to match. + + Parameters + ---------- + G : MultiDiGraph + The graph to remove an edge from. + edge_index : dict + A mapping from integers to the edges of the graph. + n : node + The node to remove from `G`. + """ + keys = set() + for keydict in G.pred[n].values(): + keys.update(keydict) + for keydict in G.succ[n].values(): + keys.update(keydict) + + for key in keys: + del edge_index[key] + + G.remove_node(n) + + ####################### + ### Algorithm Setup ### + ####################### + + # Pick an attribute name that the original graph is unlikly to have + candidate_attr = "edmonds' secret candidate attribute" + new_node_base_name = "edmonds new node base name " + + G_original = G + G = nx.MultiDiGraph() + G.__networkx_cache__ = None # Disable caching + + # A dict to reliably track mutations to the edges using the key of the edge. + G_edge_index = {} + # Each edge is given an arbitrary numerical key + for key, (u, v, data) in enumerate(G_original.edges(data=True)): + d = {attr: data.get(attr, default)} + + if data.get(partition) is not None: + d[partition] = data.get(partition) + + if preserve_attrs: + for d_k, d_v in data.items(): + if d_k != attr: + d[d_k] = d_v + + edmonds_add_edge(G, G_edge_index, u, v, key, **d) + + level = 0 # Stores the number of contracted nodes + + # These are the buckets from the paper. + # + # In the paper, G^i are modified versions of the original graph. + # D^i and E^i are the nodes and edges of the maximal edges that are + # consistent with G^i. In this implementation, D^i and E^i are stored + # together as the graph B^i. We will have strictly more B^i then the + # paper will have. + # + # Note that the data in graphs and branchings are tuples with the graph as + # the first element and the edge index as the second. + B = nx.MultiDiGraph() + B_edge_index = {} + graphs = [] # G^i list + branchings = [] # B^i list + selected_nodes = set() # D^i bucket + uf = nx.utils.UnionFind() + + # A list of lists of edge indices. Each list is a circuit for graph G^i. + # Note the edge list is not required to be a circuit in G^0. + circuits = [] + + # Stores the index of the minimum edge in the circuit found in G^i and B^i. + # The ordering of the edges seems to preserver the weight ordering from + # G^0. So even if the circuit does not form a circuit in G^0, it is still + # true that the minimum edges in circuit G^0 (despite their weights being + # different) + minedge_circuit = [] + + ########################### + ### Algorithm Structure ### + ########################### + + # Each step listed in the algorithm is an inner function. Thus, the overall + # loop structure is: + # + # while True: + # step_I1() + # if cycle detected: + # step_I2() + # elif every node of G is in D and E is a branching: + # break + + ################################## + ### Algorithm Helper Functions ### + ################################## + + def edmonds_find_desired_edge(v): + """ + Find the edge directed towards v with maximal weight. + + If an edge partition exists in this graph, return the included + edge if it exists and never return any excluded edge. + + Note: There can only be one included edge for each vertex otherwise + the edge partition is empty. + + Parameters + ---------- + v : node + The node to search for the maximal weight incoming edge. + """ + edge = None + max_weight = -INF + for u, _, key, data in G.in_edges(v, data=True, keys=True): + # Skip excluded edges + if data.get(partition) == nx.EdgePartition.EXCLUDED: + continue + + new_weight = data[attr] + + # Return the included edge + if data.get(partition) == nx.EdgePartition.INCLUDED: + max_weight = new_weight + edge = (u, v, key, new_weight, data) + break + + # Find the best open edge + if new_weight > max_weight: + max_weight = new_weight + edge = (u, v, key, new_weight, data) + + return edge, max_weight + + def edmonds_step_I2(v, desired_edge, level): + """ + Perform step I2 from Edmonds' paper + + First, check if the last step I1 created a cycle. If it did not, do nothing. + If it did, store the cycle for later reference and contract it. + + Parameters + ---------- + v : node + The current node to consider + desired_edge : edge + The minimum desired edge to remove from the cycle. + level : int + The current level, i.e. the number of cycles that have already been removed. + """ + u = desired_edge[0] + + Q_nodes = nx.shortest_path(B, v, u) + Q_edges = [ + list(B[Q_nodes[i]][vv].keys())[0] for i, vv in enumerate(Q_nodes[1:]) + ] + Q_edges.append(desired_edge[2]) # Add the new edge key to complete the circuit + + # Get the edge in the circuit with the minimum weight. + # Also, save the incoming weights for each node. + minweight = INF + minedge = None + Q_incoming_weight = {} + for edge_key in Q_edges: + u, v, data = B_edge_index[edge_key] + w = data[attr] + # We cannot remove an included edge, even if it is the + # minimum edge in the circuit + Q_incoming_weight[v] = w + if data.get(partition) == nx.EdgePartition.INCLUDED: + continue + if w < minweight: + minweight = w + minedge = edge_key + + circuits.append(Q_edges) + minedge_circuit.append(minedge) + graphs.append((G.copy(), G_edge_index.copy())) + branchings.append((B.copy(), B_edge_index.copy())) + + # Mutate the graph to contract the circuit + new_node = new_node_base_name + str(level) + G.add_node(new_node) + new_edges = [] + for u, v, key, data in G.edges(data=True, keys=True): + if u in Q_incoming_weight: + if v in Q_incoming_weight: + # Circuit edge. For the moment do nothing, + # eventually it will be removed. + continue + else: + # Outgoing edge from a node in the circuit. + # Make it come from the new node instead + dd = data.copy() + new_edges.append((new_node, v, key, dd)) + else: + if v in Q_incoming_weight: + # Incoming edge to the circuit. + # Update it's weight + w = data[attr] + w += minweight - Q_incoming_weight[v] + dd = data.copy() + dd[attr] = w + new_edges.append((u, new_node, key, dd)) + else: + # Outside edge. No modification needed + continue + + for node in Q_nodes: + edmonds_remove_node(G, G_edge_index, node) + edmonds_remove_node(B, B_edge_index, node) + + selected_nodes.difference_update(set(Q_nodes)) + + for u, v, key, data in new_edges: + edmonds_add_edge(G, G_edge_index, u, v, key, **data) + if candidate_attr in data: + del data[candidate_attr] + edmonds_add_edge(B, B_edge_index, u, v, key, **data) + uf.union(u, v) + + def is_root(G, u, edgekeys): + """ + Returns True if `u` is a root node in G. + + Node `u` is a root node if its in-degree over the specified edges is zero. + + Parameters + ---------- + G : Graph + The current graph. + u : node + The node in `G` to check if it is a root. + edgekeys : iterable of edges + The edges for which to check if `u` is a root of. + """ + if u not in G: + raise Exception(f"{u!r} not in G") + + for v in G.pred[u]: + for edgekey in G.pred[u][v]: + if edgekey in edgekeys: + return False, edgekey + else: + return True, None + + nodes = iter(list(G.nodes)) + while True: + try: + v = next(nodes) + except StopIteration: + # If there are no more new nodes to consider, then we should + # meet stopping condition (b) from the paper: + # (b) every node of G^i is in D^i and E^i is a branching + assert len(G) == len(B) + if len(B): + assert is_branching(B) + + graphs.append((G.copy(), G_edge_index.copy())) + branchings.append((B.copy(), B_edge_index.copy())) + circuits.append([]) + minedge_circuit.append(None) + + break + else: + ##################### + ### BEGIN STEP I1 ### + ##################### + + # This is a very simple step, so I don't think it needs a method of it's own + if v in selected_nodes: + continue + + selected_nodes.add(v) + B.add_node(v) + desired_edge, desired_edge_weight = edmonds_find_desired_edge(v) + + # There might be no desired edge if all edges are excluded or + # v is the last node to be added to B, the ultimate root of the branching + if desired_edge is not None and desired_edge_weight > 0: + u = desired_edge[0] + # Flag adding the edge will create a circuit before merging the two + # connected components of u and v in B + circuit = uf[u] == uf[v] + dd = {attr: desired_edge_weight} + if desired_edge[4].get(partition) is not None: + dd[partition] = desired_edge[4].get(partition) + + edmonds_add_edge(B, B_edge_index, u, v, desired_edge[2], **dd) + G[u][v][desired_edge[2]][candidate_attr] = True + uf.union(u, v) + + ################### + ### END STEP I1 ### + ################### + + ##################### + ### BEGIN STEP I2 ### + ##################### + + if circuit: + edmonds_step_I2(v, desired_edge, level) + nodes = iter(list(G.nodes())) + level += 1 + + ################### + ### END STEP I2 ### + ################### + + ##################### + ### BEGIN STEP I3 ### + ##################### + + # Create a new graph of the same class as the input graph + H = G_original.__class__() + + # Start with the branching edges in the last level. + edges = set(branchings[level][1]) + while level > 0: + level -= 1 + + # The current level is i, and we start counting from 0. + # + # We need the node at level i+1 that results from merging a circuit + # at level i. basename_0 is the first merged node and this happens + # at level 1. That is basename_0 is a node at level 1 that results + # from merging a circuit at level 0. + + merged_node = new_node_base_name + str(level) + circuit = circuits[level] + isroot, edgekey = is_root(graphs[level + 1][0], merged_node, edges) + edges.update(circuit) + + if isroot: + minedge = minedge_circuit[level] + if minedge is None: + raise Exception + + # Remove the edge in the cycle with minimum weight + edges.remove(minedge) + else: + # We have identified an edge at the next higher level that + # transitions into the merged node at this level. That edge + # transitions to some corresponding node at the current level. + # + # We want to remove an edge from the cycle that transitions + # into the corresponding node, otherwise the result would not + # be a branching. + + G, G_edge_index = graphs[level] + target = G_edge_index[edgekey][1] + for edgekey in circuit: + u, v, data = G_edge_index[edgekey] + if v == target: + break + else: + raise Exception("Couldn't find edge incoming to merged node.") + + edges.remove(edgekey) + + H.add_nodes_from(G_original) + for edgekey in edges: + u, v, d = graphs[0][1][edgekey] + dd = {attr: d[attr]} + + if preserve_attrs: + for key, value in d.items(): + if key not in [attr, candidate_attr]: + dd[key] = value + + H.add_edge(u, v, **dd) + + ################### + ### END STEP I3 ### + ################### + + return H + + +@nx._dispatchable(preserve_edge_attrs=True, mutates_input=True, returns_graph=True) +def minimum_branching( + G, attr="weight", default=1, preserve_attrs=False, partition=None +): + for _, _, d in G.edges(data=True): + d[attr] = -d.get(attr, default) + nx._clear_cache(G) + + B = maximum_branching(G, attr, default, preserve_attrs, partition) + + for _, _, d in G.edges(data=True): + d[attr] = -d.get(attr, default) + nx._clear_cache(G) + + for _, _, d in B.edges(data=True): + d[attr] = -d.get(attr, default) + nx._clear_cache(B) + + return B + + +@nx._dispatchable(preserve_edge_attrs=True, mutates_input=True, returns_graph=True) +def minimal_branching( + G, /, *, attr="weight", default=1, preserve_attrs=False, partition=None +): + """ + Returns a minimal branching from `G`. + + A minimal branching is a branching similar to a minimal arborescence but + without the requirement that the result is actually a spanning arborescence. + This allows minimal branchinges to be computed over graphs which may not + have arborescence (such as multiple components). + + Parameters + ---------- + G : (multi)digraph-like + The graph to be searched. + attr : str + The edge attribute used in determining optimality. + default : float + The value of the edge attribute used if an edge does not have + the attribute `attr`. + preserve_attrs : bool + If True, preserve the other attributes of the original graph (that are not + passed to `attr`) + partition : str + The key for the edge attribute containing the partition + data on the graph. Edges can be included, excluded or open using the + `EdgePartition` enum. + + Returns + ------- + B : (multi)digraph-like + A minimal branching. + """ + max_weight = -INF + min_weight = INF + for _, _, w in G.edges(data=attr, default=default): + if w > max_weight: + max_weight = w + if w < min_weight: + min_weight = w + + for _, _, d in G.edges(data=True): + # Transform the weights so that the minimum weight is larger than + # the difference between the max and min weights. This is important + # in order to prevent the edge weights from becoming negative during + # computation + d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default) + nx._clear_cache(G) + + B = maximum_branching(G, attr, default, preserve_attrs, partition) + + # Reverse the weight transformations + for _, _, d in G.edges(data=True): + d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default) + nx._clear_cache(G) + + for _, _, d in B.edges(data=True): + d[attr] = max_weight + 1 + (max_weight - min_weight) - d.get(attr, default) + nx._clear_cache(B) + + return B + + +@nx._dispatchable(preserve_edge_attrs=True, mutates_input=True, returns_graph=True) +def maximum_spanning_arborescence( + G, attr="weight", default=1, preserve_attrs=False, partition=None +): + # In order to use the same algorithm is the maximum branching, we need to adjust + # the weights of the graph. The branching algorithm can choose to not include an + # edge if it doesn't help find a branching, mainly triggered by edges with negative + # weights. + # + # To prevent this from happening while trying to find a spanning arborescence, we + # just have to tweak the edge weights so that they are all positive and cannot + # become negative during the branching algorithm, find the maximum branching and + # then return them to their original values. + + min_weight = INF + max_weight = -INF + for _, _, w in G.edges(data=attr, default=default): + if w < min_weight: + min_weight = w + if w > max_weight: + max_weight = w + + for _, _, d in G.edges(data=True): + d[attr] = d.get(attr, default) - min_weight + 1 - (min_weight - max_weight) + nx._clear_cache(G) + + B = maximum_branching(G, attr, default, preserve_attrs, partition) + + for _, _, d in G.edges(data=True): + d[attr] = d.get(attr, default) + min_weight - 1 + (min_weight - max_weight) + nx._clear_cache(G) + + for _, _, d in B.edges(data=True): + d[attr] = d.get(attr, default) + min_weight - 1 + (min_weight - max_weight) + nx._clear_cache(B) + + if not is_arborescence(B): + raise nx.exception.NetworkXException("No maximum spanning arborescence in G.") + + return B + + +@nx._dispatchable(preserve_edge_attrs=True, mutates_input=True, returns_graph=True) +def minimum_spanning_arborescence( + G, attr="weight", default=1, preserve_attrs=False, partition=None +): + B = minimal_branching( + G, + attr=attr, + default=default, + preserve_attrs=preserve_attrs, + partition=partition, + ) + + if not is_arborescence(B): + raise nx.exception.NetworkXException("No minimum spanning arborescence in G.") + + return B + + +docstring_branching = """ +Returns a {kind} {style} from G. + +Parameters +---------- +G : (multi)digraph-like + The graph to be searched. +attr : str + The edge attribute used to in determining optimality. +default : float + The value of the edge attribute used if an edge does not have + the attribute `attr`. +preserve_attrs : bool + If True, preserve the other attributes of the original graph (that are not + passed to `attr`) +partition : str + The key for the edge attribute containing the partition + data on the graph. Edges can be included, excluded or open using the + `EdgePartition` enum. + +Returns +------- +B : (multi)digraph-like + A {kind} {style}. +""" + +docstring_arborescence = ( + docstring_branching + + """ +Raises +------ +NetworkXException + If the graph does not contain a {kind} {style}. + +""" +) + +maximum_branching.__doc__ = docstring_branching.format( + kind="maximum", style="branching" +) + +minimum_branching.__doc__ = ( + docstring_branching.format(kind="minimum", style="branching") + + """ +See Also +-------- + minimal_branching +""" +) + +maximum_spanning_arborescence.__doc__ = docstring_arborescence.format( + kind="maximum", style="spanning arborescence" +) + +minimum_spanning_arborescence.__doc__ = docstring_arborescence.format( + kind="minimum", style="spanning arborescence" +) + + +class ArborescenceIterator: + """ + Iterate over all spanning arborescences of a graph in either increasing or + decreasing cost. + + Notes + ----- + This iterator uses the partition scheme from [1]_ (included edges, + excluded edges and open edges). It generates minimum spanning + arborescences using a modified Edmonds' Algorithm which respects the + partition of edges. For arborescences with the same weight, ties are + broken arbitrarily. + + References + ---------- + .. [1] G.K. Janssens, K. Sörensen, An algorithm to generate all spanning + trees in order of increasing cost, Pesquisa Operacional, 2005-08, + Vol. 25 (2), p. 219-229, + https://www.scielo.br/j/pope/a/XHswBwRwJyrfL88dmMwYNWp/?lang=en + """ + + @dataclass(order=True) + class Partition: + """ + This dataclass represents a partition and stores a dict with the edge + data and the weight of the minimum spanning arborescence of the + partition dict. + """ + + mst_weight: float + partition_dict: dict = field(compare=False) + + def __copy__(self): + return ArborescenceIterator.Partition( + self.mst_weight, self.partition_dict.copy() + ) + + def __init__(self, G, weight="weight", minimum=True, init_partition=None): + """ + Initialize the iterator + + Parameters + ---------- + G : nx.DiGraph + The directed graph which we need to iterate trees over + + weight : String, default = "weight" + The edge attribute used to store the weight of the edge + + minimum : bool, default = True + Return the trees in increasing order while true and decreasing order + while false. + + init_partition : tuple, default = None + In the case that certain edges have to be included or excluded from + the arborescences, `init_partition` should be in the form + `(included_edges, excluded_edges)` where each edges is a + `(u, v)`-tuple inside an iterable such as a list or set. + + """ + self.G = G.copy() + self.weight = weight + self.minimum = minimum + self.method = ( + minimum_spanning_arborescence if minimum else maximum_spanning_arborescence + ) + # Randomly create a key for an edge attribute to hold the partition data + self.partition_key = ( + "ArborescenceIterators super secret partition attribute name" + ) + if init_partition is not None: + partition_dict = {} + for e in init_partition[0]: + partition_dict[e] = nx.EdgePartition.INCLUDED + for e in init_partition[1]: + partition_dict[e] = nx.EdgePartition.EXCLUDED + self.init_partition = ArborescenceIterator.Partition(0, partition_dict) + else: + self.init_partition = None + + def __iter__(self): + """ + Returns + ------- + ArborescenceIterator + The iterator object for this graph + """ + self.partition_queue = PriorityQueue() + self._clear_partition(self.G) + + # Write the initial partition if it exists. + if self.init_partition is not None: + self._write_partition(self.init_partition) + + mst_weight = self.method( + self.G, + self.weight, + partition=self.partition_key, + preserve_attrs=True, + ).size(weight=self.weight) + + self.partition_queue.put( + self.Partition( + mst_weight if self.minimum else -mst_weight, + ( + {} + if self.init_partition is None + else self.init_partition.partition_dict + ), + ) + ) + + return self + + def __next__(self): + """ + Returns + ------- + (multi)Graph + The spanning tree of next greatest weight, which ties broken + arbitrarily. + """ + if self.partition_queue.empty(): + del self.G, self.partition_queue + raise StopIteration + + partition = self.partition_queue.get() + self._write_partition(partition) + next_arborescence = self.method( + self.G, + self.weight, + partition=self.partition_key, + preserve_attrs=True, + ) + self._partition(partition, next_arborescence) + + self._clear_partition(next_arborescence) + return next_arborescence + + def _partition(self, partition, partition_arborescence): + """ + Create new partitions based of the minimum spanning tree of the + current minimum partition. + + Parameters + ---------- + partition : Partition + The Partition instance used to generate the current minimum spanning + tree. + partition_arborescence : nx.Graph + The minimum spanning arborescence of the input partition. + """ + # create two new partitions with the data from the input partition dict + p1 = self.Partition(0, partition.partition_dict.copy()) + p2 = self.Partition(0, partition.partition_dict.copy()) + for e in partition_arborescence.edges: + # determine if the edge was open or included + if e not in partition.partition_dict: + # This is an open edge + p1.partition_dict[e] = nx.EdgePartition.EXCLUDED + p2.partition_dict[e] = nx.EdgePartition.INCLUDED + + self._write_partition(p1) + try: + p1_mst = self.method( + self.G, + self.weight, + partition=self.partition_key, + preserve_attrs=True, + ) + + p1_mst_weight = p1_mst.size(weight=self.weight) + p1.mst_weight = p1_mst_weight if self.minimum else -p1_mst_weight + self.partition_queue.put(p1.__copy__()) + except nx.NetworkXException: + pass + + p1.partition_dict = p2.partition_dict.copy() + + def _write_partition(self, partition): + """ + Writes the desired partition into the graph to calculate the minimum + spanning tree. Also, if one incoming edge is included, mark all others + as excluded so that if that vertex is merged during Edmonds' algorithm + we cannot still pick another of that vertex's included edges. + + Parameters + ---------- + partition : Partition + A Partition dataclass describing a partition on the edges of the + graph. + """ + for u, v, d in self.G.edges(data=True): + if (u, v) in partition.partition_dict: + d[self.partition_key] = partition.partition_dict[(u, v)] + else: + d[self.partition_key] = nx.EdgePartition.OPEN + nx._clear_cache(self.G) + + for n in self.G: + included_count = 0 + excluded_count = 0 + for u, v, d in self.G.in_edges(nbunch=n, data=True): + if d.get(self.partition_key) == nx.EdgePartition.INCLUDED: + included_count += 1 + elif d.get(self.partition_key) == nx.EdgePartition.EXCLUDED: + excluded_count += 1 + # Check that if there is an included edges, all other incoming ones + # are excluded. If not fix it! + if included_count == 1 and excluded_count != self.G.in_degree(n) - 1: + for u, v, d in self.G.in_edges(nbunch=n, data=True): + if d.get(self.partition_key) != nx.EdgePartition.INCLUDED: + d[self.partition_key] = nx.EdgePartition.EXCLUDED + + def _clear_partition(self, G): + """ + Removes partition data from the graph + """ + for u, v, d in G.edges(data=True): + if self.partition_key in d: + del d[self.partition_key] + nx._clear_cache(self.G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py new file mode 100644 index 0000000000000000000000000000000000000000..276c87d0745dade0f11bb9374c3479e1f9f8bcfe --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/coding.py @@ -0,0 +1,413 @@ +"""Functions for encoding and decoding trees. + +Since a tree is a highly restricted form of graph, it can be represented +concisely in several ways. This module includes functions for encoding +and decoding trees in the form of nested tuples and Prüfer +sequences. The former requires a rooted tree, whereas the latter can be +applied to unrooted trees. Furthermore, there is a bijection from Prüfer +sequences to labeled trees. + +""" + +from collections import Counter +from itertools import chain + +import networkx as nx +from networkx.utils import not_implemented_for + +__all__ = [ + "from_nested_tuple", + "from_prufer_sequence", + "NotATree", + "to_nested_tuple", + "to_prufer_sequence", +] + + +class NotATree(nx.NetworkXException): + """Raised when a function expects a tree (that is, a connected + undirected graph with no cycles) but gets a non-tree graph as input + instead. + + """ + + +@not_implemented_for("directed") +@nx._dispatchable(graphs="T") +def to_nested_tuple(T, root, canonical_form=False): + """Returns a nested tuple representation of the given tree. + + The nested tuple representation of a tree is defined + recursively. The tree with one node and no edges is represented by + the empty tuple, ``()``. A tree with ``k`` subtrees is represented + by a tuple of length ``k`` in which each element is the nested tuple + representation of a subtree. + + Parameters + ---------- + T : NetworkX graph + An undirected graph object representing a tree. + + root : node + The node in ``T`` to interpret as the root of the tree. + + canonical_form : bool + If ``True``, each tuple is sorted so that the function returns + a canonical form for rooted trees. This means "lighter" subtrees + will appear as nested tuples before "heavier" subtrees. In this + way, each isomorphic rooted tree has the same nested tuple + representation. + + Returns + ------- + tuple + A nested tuple representation of the tree. + + Notes + ----- + This function is *not* the inverse of :func:`from_nested_tuple`; the + only guarantee is that the rooted trees are isomorphic. + + See also + -------- + from_nested_tuple + to_prufer_sequence + + Examples + -------- + The tree need not be a balanced binary tree:: + + >>> T = nx.Graph() + >>> T.add_edges_from([(0, 1), (0, 2), (0, 3)]) + >>> T.add_edges_from([(1, 4), (1, 5)]) + >>> T.add_edges_from([(3, 6), (3, 7)]) + >>> root = 0 + >>> nx.to_nested_tuple(T, root) + (((), ()), (), ((), ())) + + Continuing the above example, if ``canonical_form`` is ``True``, the + nested tuples will be sorted:: + + >>> nx.to_nested_tuple(T, root, canonical_form=True) + ((), ((), ()), ((), ())) + + Even the path graph can be interpreted as a tree:: + + >>> T = nx.path_graph(4) + >>> root = 0 + >>> nx.to_nested_tuple(T, root) + ((((),),),) + + """ + + def _make_tuple(T, root, _parent): + """Recursively compute the nested tuple representation of the + given rooted tree. + + ``_parent`` is the parent node of ``root`` in the supertree in + which ``T`` is a subtree, or ``None`` if ``root`` is the root of + the supertree. This argument is used to determine which + neighbors of ``root`` are children and which is the parent. + + """ + # Get the neighbors of `root` that are not the parent node. We + # are guaranteed that `root` is always in `T` by construction. + children = set(T[root]) - {_parent} + if len(children) == 0: + return () + nested = (_make_tuple(T, v, root) for v in children) + if canonical_form: + nested = sorted(nested) + return tuple(nested) + + # Do some sanity checks on the input. + if not nx.is_tree(T): + raise nx.NotATree("provided graph is not a tree") + if root not in T: + raise nx.NodeNotFound(f"Graph {T} contains no node {root}") + + return _make_tuple(T, root, None) + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_nested_tuple(sequence, sensible_relabeling=False): + """Returns the rooted tree corresponding to the given nested tuple. + + The nested tuple representation of a tree is defined + recursively. The tree with one node and no edges is represented by + the empty tuple, ``()``. A tree with ``k`` subtrees is represented + by a tuple of length ``k`` in which each element is the nested tuple + representation of a subtree. + + Parameters + ---------- + sequence : tuple + A nested tuple representing a rooted tree. + + sensible_relabeling : bool + Whether to relabel the nodes of the tree so that nodes are + labeled in increasing order according to their breadth-first + search order from the root node. + + Returns + ------- + NetworkX graph + The tree corresponding to the given nested tuple, whose root + node is node 0. If ``sensible_labeling`` is ``True``, nodes will + be labeled in breadth-first search order starting from the root + node. + + Notes + ----- + This function is *not* the inverse of :func:`to_nested_tuple`; the + only guarantee is that the rooted trees are isomorphic. + + See also + -------- + to_nested_tuple + from_prufer_sequence + + Examples + -------- + Sensible relabeling ensures that the nodes are labeled from the root + starting at 0:: + + >>> balanced = (((), ()), ((), ())) + >>> T = nx.from_nested_tuple(balanced, sensible_relabeling=True) + >>> edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)] + >>> all((u, v) in T.edges() or (v, u) in T.edges() for (u, v) in edges) + True + + """ + + def _make_tree(sequence): + """Recursively creates a tree from the given sequence of nested + tuples. + + This function employs the :func:`~networkx.tree.join` function + to recursively join subtrees into a larger tree. + + """ + # The empty sequence represents the empty tree, which is the + # (unique) graph with a single node. We mark the single node + # with an attribute that indicates that it is the root of the + # graph. + if len(sequence) == 0: + return nx.empty_graph(1) + # For a nonempty sequence, get the subtrees for each child + # sequence and join all the subtrees at their roots. After + # joining the subtrees, the root is node 0. + return nx.tree.join_trees([(_make_tree(child), 0) for child in sequence]) + + # Make the tree and remove the `is_root` node attribute added by the + # helper function. + T = _make_tree(sequence) + if sensible_relabeling: + # Relabel the nodes according to their breadth-first search + # order, starting from the root node (that is, the node 0). + bfs_nodes = chain([0], (v for u, v in nx.bfs_edges(T, 0))) + labels = {v: i for i, v in enumerate(bfs_nodes)} + # We would like to use `copy=False`, but `relabel_nodes` doesn't + # allow a relabel mapping that can't be topologically sorted. + T = nx.relabel_nodes(T, labels) + return T + + +@not_implemented_for("directed") +@nx._dispatchable(graphs="T") +def to_prufer_sequence(T): + r"""Returns the Prüfer sequence of the given tree. + + A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and + *n* - 1, inclusive. The tree corresponding to a given Prüfer + sequence can be recovered by repeatedly joining a node in the + sequence with a node with the smallest potential degree according to + the sequence. + + Parameters + ---------- + T : NetworkX graph + An undirected graph object representing a tree. + + Returns + ------- + list + The Prüfer sequence of the given tree. + + Raises + ------ + NetworkXPointlessConcept + If the number of nodes in `T` is less than two. + + NotATree + If `T` is not a tree. + + KeyError + If the set of nodes in `T` is not {0, …, *n* - 1}. + + Notes + ----- + There is a bijection from labeled trees to Prüfer sequences. This + function is the inverse of the :func:`from_prufer_sequence` + function. + + Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead + of from 0 to *n* - 1. This function requires nodes to be labeled in + the latter form. You can use :func:`~networkx.relabel_nodes` to + relabel the nodes of your tree to the appropriate format. + + This implementation is from [1]_ and has a running time of + $O(n)$. + + See also + -------- + to_nested_tuple + from_prufer_sequence + + References + ---------- + .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu. + "An optimal algorithm for Prufer codes." + *Journal of Software Engineering and Applications* 2.02 (2009): 111. + + + Examples + -------- + There is a bijection between Prüfer sequences and labeled trees, so + this function is the inverse of the :func:`from_prufer_sequence` + function: + + >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)] + >>> tree = nx.Graph(edges) + >>> sequence = nx.to_prufer_sequence(tree) + >>> sequence + [3, 3, 3, 4] + >>> tree2 = nx.from_prufer_sequence(sequence) + >>> list(tree2.edges()) == edges + True + + """ + # Perform some sanity checks on the input. + n = len(T) + if n < 2: + msg = "Prüfer sequence undefined for trees with fewer than two nodes" + raise nx.NetworkXPointlessConcept(msg) + if not nx.is_tree(T): + raise nx.NotATree("provided graph is not a tree") + if set(T) != set(range(n)): + raise KeyError("tree must have node labels {0, ..., n - 1}") + + degree = dict(T.degree()) + + def parents(u): + return next(v for v in T[u] if degree[v] > 1) + + index = u = next(k for k in range(n) if degree[k] == 1) + result = [] + for i in range(n - 2): + v = parents(u) + result.append(v) + degree[v] -= 1 + if v < index and degree[v] == 1: + u = v + else: + index = u = next(k for k in range(index + 1, n) if degree[k] == 1) + return result + + +@nx._dispatchable(graphs=None, returns_graph=True) +def from_prufer_sequence(sequence): + r"""Returns the tree corresponding to the given Prüfer sequence. + + A *Prüfer sequence* is a list of *n* - 2 numbers between 0 and + *n* - 1, inclusive. The tree corresponding to a given Prüfer + sequence can be recovered by repeatedly joining a node in the + sequence with a node with the smallest potential degree according to + the sequence. + + Parameters + ---------- + sequence : list + A Prüfer sequence, which is a list of *n* - 2 integers between + zero and *n* - 1, inclusive. + + Returns + ------- + NetworkX graph + The tree corresponding to the given Prüfer sequence. + + Raises + ------ + NetworkXError + If the Prüfer sequence is not valid. + + Notes + ----- + There is a bijection from labeled trees to Prüfer sequences. This + function is the inverse of the :func:`from_prufer_sequence` function. + + Sometimes Prüfer sequences use nodes labeled from 1 to *n* instead + of from 0 to *n* - 1. This function requires nodes to be labeled in + the latter form. You can use :func:`networkx.relabel_nodes` to + relabel the nodes of your tree to the appropriate format. + + This implementation is from [1]_ and has a running time of + $O(n)$. + + References + ---------- + .. [1] Wang, Xiaodong, Lei Wang, and Yingjie Wu. + "An optimal algorithm for Prufer codes." + *Journal of Software Engineering and Applications* 2.02 (2009): 111. + + + See also + -------- + from_nested_tuple + to_prufer_sequence + + Examples + -------- + There is a bijection between Prüfer sequences and labeled trees, so + this function is the inverse of the :func:`to_prufer_sequence` + function: + + >>> edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)] + >>> tree = nx.Graph(edges) + >>> sequence = nx.to_prufer_sequence(tree) + >>> sequence + [3, 3, 3, 4] + >>> tree2 = nx.from_prufer_sequence(sequence) + >>> list(tree2.edges()) == edges + True + + """ + n = len(sequence) + 2 + # `degree` stores the remaining degree (plus one) for each node. The + # degree of a node in the decoded tree is one more than the number + # of times it appears in the code. + degree = Counter(chain(sequence, range(n))) + T = nx.empty_graph(n) + # `not_orphaned` is the set of nodes that have a parent in the + # tree. After the loop, there should be exactly two nodes that are + # not in this set. + not_orphaned = set() + index = u = next(k for k in range(n) if degree[k] == 1) + for v in sequence: + # check the validity of the prufer sequence + if v < 0 or v > n - 1: + raise nx.NetworkXError( + f"Invalid Prufer sequence: Values must be between 0 and {n - 1}, got {v}" + ) + T.add_edge(u, v) + not_orphaned.add(u) + degree[v] -= 1 + if v < index and degree[v] == 1: + u = v + else: + index = u = next(k for k in range(index + 1, n) if degree[k] == 1) + # At this point, there must be exactly two orphaned nodes; join them. + orphans = set(T) - not_orphaned + u, v = orphans + T.add_edge(u, v) + return T diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/decomposition.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/decomposition.py new file mode 100644 index 0000000000000000000000000000000000000000..c8b8f2477b47581cd6010aba7e3329f5044e0da4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/decomposition.py @@ -0,0 +1,88 @@ +r"""Function for computing a junction tree of a graph.""" + +from itertools import combinations + +import networkx as nx +from networkx.algorithms import chordal_graph_cliques, complete_to_chordal_graph, moral +from networkx.utils import not_implemented_for + +__all__ = ["junction_tree"] + + +@not_implemented_for("multigraph") +@nx._dispatchable(returns_graph=True) +def junction_tree(G): + r"""Returns a junction tree of a given graph. + + A junction tree (or clique tree) is constructed from a (un)directed graph G. + The tree is constructed based on a moralized and triangulated version of G. + The tree's nodes consist of maximal cliques and sepsets of the revised graph. + The sepset of two cliques is the intersection of the nodes of these cliques, + e.g. the sepset of (A,B,C) and (A,C,E,F) is (A,C). These nodes are often called + "variables" in this literature. The tree is bipartite with each sepset + connected to its two cliques. + + Junction Trees are not unique as the order of clique consideration determines + which sepsets are included. + + The junction tree algorithm consists of five steps [1]_: + + 1. Moralize the graph + 2. Triangulate the graph + 3. Find maximal cliques + 4. Build the tree from cliques, connecting cliques with shared + nodes, set edge-weight to number of shared variables + 5. Find maximum spanning tree + + + Parameters + ---------- + G : networkx.Graph + Directed or undirected graph. + + Returns + ------- + junction_tree : networkx.Graph + The corresponding junction tree of `G`. + + Raises + ------ + NetworkXNotImplemented + Raised if `G` is an instance of `MultiGraph` or `MultiDiGraph`. + + References + ---------- + .. [1] Junction tree algorithm: + https://en.wikipedia.org/wiki/Junction_tree_algorithm + + .. [2] Finn V. Jensen and Frank Jensen. 1994. Optimal + junction trees. In Proceedings of the Tenth international + conference on Uncertainty in artificial intelligence (UAI’94). + Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 360–366. + """ + + clique_graph = nx.Graph() + + if G.is_directed(): + G = moral.moral_graph(G) + chordal_graph, _ = complete_to_chordal_graph(G) + + cliques = [tuple(sorted(i)) for i in chordal_graph_cliques(chordal_graph)] + clique_graph.add_nodes_from(cliques, type="clique") + + for edge in combinations(cliques, 2): + set_edge_0 = set(edge[0]) + set_edge_1 = set(edge[1]) + if not set_edge_0.isdisjoint(set_edge_1): + sepset = tuple(sorted(set_edge_0.intersection(set_edge_1))) + clique_graph.add_edge(edge[0], edge[1], weight=len(sepset), sepset=sepset) + + junction_tree = nx.maximum_spanning_tree(clique_graph) + + for edge in list(junction_tree.edges(data=True)): + junction_tree.add_node(edge[2]["sepset"], type="sepset") + junction_tree.add_edge(edge[0], edge[2]["sepset"]) + junction_tree.add_edge(edge[1], edge[2]["sepset"]) + junction_tree.remove_edge(edge[0], edge[1]) + + return junction_tree diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/distance_measures.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/distance_measures.py new file mode 100644 index 0000000000000000000000000000000000000000..6d0b273737686d561f88504b937f1267e0fec106 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/distance_measures.py @@ -0,0 +1,219 @@ +""" +Algorithms for computing distance measures on trees. +""" + +import networkx as nx + +__all__ = [ + "center", + "centroid", +] + + +@nx.utils.not_implemented_for("directed") +def center(G): + """Returns the center of an undirected tree graph. + + The center of a tree consists of nodes that minimize the maximum eccentricity. + That is, these nodes minimize the maximum distance to all other nodes. + This implementation currently only works for unweighted edges. + + If the input graph is not a tree, results are not guaranteed to be correct and while + some non-trees will raise an ``nx.NotATree`` exception, not all non-trees will be discovered. + Thus, this function should not be used if caller is unsure whether the input graph + is a tree. Use ``nx.is_tree(G)`` to check. + + Parameters + ---------- + G : NetworkX graph + A tree graph (undirected, acyclic graph). + + Returns + ------- + center : list + A list of nodes forming the center of the tree. This can be one or two nodes. + + Raises + ------ + NetworkXNotImplemented + If the input graph is directed. + + NotATree + If the algorithm detects the input graph is not a tree. There is no guarantee + this error will always raise if a non-tree is passed. + + Notes + ----- + This algorithm iteratively removes leaves (nodes with degree 1) from the tree until + there are only 1 or 2 nodes left. The remaining nodes form the center of the tree. + + This algorithm's time complexity is ``O(N)`` where ``N`` is the number of nodes in the tree. + + Examples + -------- + >>> G = nx.Graph([(1, 2), (1, 3), (2, 4), (2, 5)]) + >>> nx.tree.center(G) + [1, 2] + + >>> G = nx.path_graph(5) + >>> nx.tree.center(G) + [2] + """ + center_candidates_degree = dict(G.degree) + leaves = {node for node, degree in center_candidates_degree.items() if degree == 1} + + # It's better to fail than an infinite loop, so check leaves to ensure progress. + while len(center_candidates_degree) > 2 and leaves: + new_leaves = set() + for leaf in leaves: + del center_candidates_degree[leaf] + for neighbor in G.neighbors(leaf): + if neighbor not in center_candidates_degree: + continue + center_candidates_degree[neighbor] -= 1 + if (cddn := center_candidates_degree[neighbor]) == 1: + new_leaves.add(neighbor) + elif cddn == 0 and len(center_candidates_degree) != 1: + raise nx.NotATree("input graph is not a tree") + leaves = new_leaves + + n = len(center_candidates_degree) + # check disconnected or cyclic + if (n == 2 and not leaves) or n not in {1, 2}: + # We have detected graph is not a tree. This check does not cover all cases. + # For example, `nx.Graph([(0, 0)])` will not raise an error. + raise nx.NotATree("input graph is not a tree") + + return list(center_candidates_degree) + + +def _subtree_sizes(G, root): + """Return a `dict` of the size of each subtree, for every subtree + of a tree rooted at a given node. + + For every node in the given tree, consider the new tree that would + be created by detaching it from its parent node (if any). The + number of nodes in the resulting tree rooted at that node is then + assigned as the value for that node in the return dictionary. + + Parameters + ---------- + G : NetworkX graph + A tree. + + root : node + A node in `G`. + + Returns + ------- + s : dict + Dictionary of number of nodes in every subtree of this tree, + keyed on the root node for each subtree. + + Examples + -------- + >>> _subtree_sizes(nx.path_graph(4), 0) + {0: 4, 1: 3, 2: 2, 3: 1} + + >>> _subtree_sizes(nx.path_graph(4), 2) + {2: 4, 1: 2, 0: 1, 3: 1} + + """ + sizes = {root: 1} + stack = [root] + for parent, child in nx.dfs_edges(G, root): + while stack[-1] != parent: + descendant = stack.pop() + sizes[stack[-1]] += sizes[descendant] + stack.append(child) + sizes[child] = 1 + for child, parent in nx.utils.pairwise(reversed(stack)): + sizes[parent] += sizes[child] + return sizes + + +@nx.utils.not_implemented_for("directed") +@nx._dispatchable +def centroid(G): + """Return the centroid of an unweighted tree. + + The centroid of a tree is the set of nodes such that removing any + one of them would split the tree into a forest of subtrees, each + with at most ``N / 2`` nodes, where ``N`` is the number of nodes + in the original tree. This set may contain two nodes if removing + an edge between them results in two trees of size exactly ``N / + 2``. + + Parameters + ---------- + G : NetworkX graph + A tree. + + Returns + ------- + c : list + List of nodes in centroid of the tree. This could be one or two nodes. + + Raises + ------ + NotATree + If the input graph is not a tree. + NotImplementedException + If the input graph is directed. + NetworkXPointlessConcept + If `G` has no nodes or edges. + + Notes + ----- + This algorithm's time complexity is ``O(N)`` where ``N`` is the + number of nodes in the tree. + + In unweighted trees the centroid coincides with the barycenter, + the node or nodes that minimize the sum of distances to all other + nodes. However, this concept is different from that of the graph + center, which is the set of nodes minimizing the maximum distance + to all other nodes. + + Examples + -------- + >>> G = nx.path_graph(4) + >>> nx.tree.centroid(G) + [1, 2] + + A star-shaped tree with one long branch illustrates the difference + between the centroid and the center. The center lies near the + middle of the long branch, minimizing maximum distance. The + centroid, however, limits the size of any resulting subtree to at + most half the total nodes, forcing it to remain near the hub when + enough short branches are present. + + >>> G = nx.star_graph(6) + >>> nx.add_path(G, [6, 7, 8, 9, 10]) + >>> nx.tree.centroid(G), nx.tree.center(G) + ([0], [7]) + + See Also + -------- + :func:`~networkx.algorithms.distance_measures.barycenter` + :func:`~networkx.algorithms.distance_measures.center` + center : tree center + """ + if not nx.is_tree(G): + raise nx.NotATree("provided graph is not a tree") + prev, root = None, nx.utils.arbitrary_element(G) + sizes = _subtree_sizes(G, root) + total_size = G.number_of_nodes() + + def _heaviest_child(prev, root): + return max( + (x for x in G.neighbors(root) if x != prev), key=sizes.get, default=None + ) + + hc = _heaviest_child(prev, root) + while max(total_size - sizes[root], sizes.get(hc, 0)) > total_size / 2: + prev, root = root, hc + hc = _heaviest_child(prev, root) + + return [root] + [ + x for x in G.neighbors(root) if x != prev and sizes[x] == total_size / 2 + ] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py new file mode 100644 index 0000000000000000000000000000000000000000..5886c6ef8a5e951550187917534e445c5a943cf5 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/mst.py @@ -0,0 +1,1281 @@ +""" +Algorithms for calculating min/max spanning trees/forests. + +""" + +from dataclasses import dataclass, field +from enum import Enum +from heapq import heappop, heappush +from itertools import count +from math import isnan +from operator import itemgetter +from queue import PriorityQueue + +import networkx as nx +from networkx.utils import UnionFind, not_implemented_for, py_random_state + +__all__ = [ + "minimum_spanning_edges", + "maximum_spanning_edges", + "minimum_spanning_tree", + "maximum_spanning_tree", + "number_of_spanning_trees", + "random_spanning_tree", + "partition_spanning_tree", + "EdgePartition", + "SpanningTreeIterator", +] + + +class EdgePartition(Enum): + """ + An enum to store the state of an edge partition. The enum is written to the + edges of a graph before being pasted to `kruskal_mst_edges`. Options are: + + - EdgePartition.OPEN + - EdgePartition.INCLUDED + - EdgePartition.EXCLUDED + """ + + OPEN = 0 + INCLUDED = 1 + EXCLUDED = 2 + + +@not_implemented_for("multigraph") +@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data") +def boruvka_mst_edges( + G, minimum=True, weight="weight", keys=False, data=True, ignore_nan=False +): + """Iterate over edges of a Borůvka's algorithm min/max spanning tree. + + Parameters + ---------- + G : NetworkX Graph + The edges of `G` must have distinct weights, + otherwise the edges may not form a tree. + + minimum : bool (default: True) + Find the minimum (True) or maximum (False) spanning tree. + + weight : string (default: 'weight') + The name of the edge attribute holding the edge weights. + + keys : bool (default: True) + This argument is ignored since this function is not + implemented for multigraphs; it exists only for consistency + with the other minimum spanning tree functions. + + data : bool (default: True) + Flag for whether to yield edge attribute dicts. + If True, yield edges `(u, v, d)`, where `d` is the attribute dict. + If False, yield edges `(u, v)`. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + """ + # Initialize a forest, assuming initially that it is the discrete + # partition of the nodes of the graph. + forest = UnionFind(G) + + def best_edge(component): + """Returns the optimum (minimum or maximum) edge on the edge + boundary of the given set of nodes. + + A return value of ``None`` indicates an empty boundary. + + """ + sign = 1 if minimum else -1 + minwt = float("inf") + boundary = None + for e in nx.edge_boundary(G, component, data=True): + wt = e[-1].get(weight, 1) * sign + if isnan(wt): + if ignore_nan: + continue + msg = f"NaN found as an edge weight. Edge {e}" + raise ValueError(msg) + if wt < minwt: + minwt = wt + boundary = e + return boundary + + # Determine the optimum edge in the edge boundary of each component + # in the forest. + best_edges = (best_edge(component) for component in forest.to_sets()) + best_edges = [edge for edge in best_edges if edge is not None] + # If each entry was ``None``, that means the graph was disconnected, + # so we are done generating the forest. + while best_edges: + # Determine the optimum edge in the edge boundary of each + # component in the forest. + # + # This must be a sequence, not an iterator. In this list, the + # same edge may appear twice, in different orientations (but + # that's okay, since a union operation will be called on the + # endpoints the first time it is seen, but not the second time). + # + # Any ``None`` indicates that the edge boundary for that + # component was empty, so that part of the forest has been + # completed. + # + # TODO This can be parallelized, both in the outer loop over + # each component in the forest and in the computation of the + # minimum. (Same goes for the identical lines outside the loop.) + best_edges = (best_edge(component) for component in forest.to_sets()) + best_edges = [edge for edge in best_edges if edge is not None] + # Join trees in the forest using the best edges, and yield that + # edge, since it is part of the spanning tree. + # + # TODO This loop can be parallelized, to an extent (the union + # operation must be atomic). + for u, v, d in best_edges: + if forest[u] != forest[v]: + if data: + yield u, v, d + else: + yield u, v + forest.union(u, v) + + +@nx._dispatchable( + edge_attrs={"weight": None, "partition": None}, preserve_edge_attrs="data" +) +def kruskal_mst_edges( + G, minimum, weight="weight", keys=True, data=True, ignore_nan=False, partition=None +): + """ + Iterate over edge of a Kruskal's algorithm min/max spanning tree. + + Parameters + ---------- + G : NetworkX Graph + The graph holding the tree of interest. + + minimum : bool (default: True) + Find the minimum (True) or maximum (False) spanning tree. + + weight : string (default: 'weight') + The name of the edge attribute holding the edge weights. + + keys : bool (default: True) + If `G` is a multigraph, `keys` controls whether edge keys ar yielded. + Otherwise `keys` is ignored. + + data : bool (default: True) + Flag for whether to yield edge attribute dicts. + If True, yield edges `(u, v, d)`, where `d` is the attribute dict. + If False, yield edges `(u, v)`. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + partition : string (default: None) + The name of the edge attribute holding the partition data, if it exists. + Partition data is written to the edges using the `EdgePartition` enum. + If a partition exists, all included edges and none of the excluded edges + will appear in the final tree. Open edges may or may not be used. + + Yields + ------ + edge tuple + The edges as discovered by Kruskal's method. Each edge can + take the following forms: `(u, v)`, `(u, v, d)` or `(u, v, k, d)` + depending on the `key` and `data` parameters + """ + subtrees = UnionFind() + if G.is_multigraph(): + edges = G.edges(keys=True, data=True) + else: + edges = G.edges(data=True) + + # Sort the edges of the graph with respect to the partition data. + # Edges are returned in the following order: + + # * Included edges + # * Open edges from smallest to largest weight + # * Excluded edges + included_edges = [] + open_edges = [] + for e in edges: + d = e[-1] + wt = d.get(weight, 1) + if isnan(wt): + if ignore_nan: + continue + raise ValueError(f"NaN found as an edge weight. Edge {e}") + + edge = (wt,) + e + if d.get(partition) == EdgePartition.INCLUDED: + included_edges.append(edge) + elif d.get(partition) == EdgePartition.EXCLUDED: + continue + else: + open_edges.append(edge) + + if minimum: + sorted_open_edges = sorted(open_edges, key=itemgetter(0)) + else: + sorted_open_edges = sorted(open_edges, key=itemgetter(0), reverse=True) + + # Condense the lists into one + included_edges.extend(sorted_open_edges) + sorted_edges = included_edges + del open_edges, sorted_open_edges, included_edges + + # Multigraphs need to handle edge keys in addition to edge data. + if G.is_multigraph(): + for wt, u, v, k, d in sorted_edges: + if subtrees[u] != subtrees[v]: + if keys: + if data: + yield u, v, k, d + else: + yield u, v, k + else: + if data: + yield u, v, d + else: + yield u, v + subtrees.union(u, v) + else: + for wt, u, v, d in sorted_edges: + if subtrees[u] != subtrees[v]: + if data: + yield u, v, d + else: + yield u, v + subtrees.union(u, v) + + +@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data") +def prim_mst_edges(G, minimum, weight="weight", keys=True, data=True, ignore_nan=False): + """Iterate over edges of Prim's algorithm min/max spanning tree. + + Parameters + ---------- + G : NetworkX Graph + The graph holding the tree of interest. + + minimum : bool (default: True) + Find the minimum (True) or maximum (False) spanning tree. + + weight : string (default: 'weight') + The name of the edge attribute holding the edge weights. + + keys : bool (default: True) + If `G` is a multigraph, `keys` controls whether edge keys ar yielded. + Otherwise `keys` is ignored. + + data : bool (default: True) + Flag for whether to yield edge attribute dicts. + If True, yield edges `(u, v, d)`, where `d` is the attribute dict. + If False, yield edges `(u, v)`. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + """ + is_multigraph = G.is_multigraph() + + nodes = set(G) + c = count() + + sign = 1 if minimum else -1 + + while nodes: + u = nodes.pop() + frontier = [] + visited = {u} + if is_multigraph: + for v, keydict in G.adj[u].items(): + for k, d in keydict.items(): + wt = d.get(weight, 1) * sign + if isnan(wt): + if ignore_nan: + continue + msg = f"NaN found as an edge weight. Edge {(u, v, k, d)}" + raise ValueError(msg) + heappush(frontier, (wt, next(c), u, v, k, d)) + else: + for v, d in G.adj[u].items(): + wt = d.get(weight, 1) * sign + if isnan(wt): + if ignore_nan: + continue + msg = f"NaN found as an edge weight. Edge {(u, v, d)}" + raise ValueError(msg) + heappush(frontier, (wt, next(c), u, v, d)) + while nodes and frontier: + if is_multigraph: + W, _, u, v, k, d = heappop(frontier) + else: + W, _, u, v, d = heappop(frontier) + if v in visited or v not in nodes: + continue + # Multigraphs need to handle edge keys in addition to edge data. + if is_multigraph and keys: + if data: + yield u, v, k, d + else: + yield u, v, k + else: + if data: + yield u, v, d + else: + yield u, v + # update frontier + visited.add(v) + nodes.discard(v) + if is_multigraph: + for w, keydict in G.adj[v].items(): + if w in visited: + continue + for k2, d2 in keydict.items(): + new_weight = d2.get(weight, 1) * sign + if isnan(new_weight): + if ignore_nan: + continue + msg = f"NaN found as an edge weight. Edge {(v, w, k2, d2)}" + raise ValueError(msg) + heappush(frontier, (new_weight, next(c), v, w, k2, d2)) + else: + for w, d2 in G.adj[v].items(): + if w in visited: + continue + new_weight = d2.get(weight, 1) * sign + if isnan(new_weight): + if ignore_nan: + continue + msg = f"NaN found as an edge weight. Edge {(v, w, d2)}" + raise ValueError(msg) + heappush(frontier, (new_weight, next(c), v, w, d2)) + + +ALGORITHMS = { + "boruvka": boruvka_mst_edges, + "borůvka": boruvka_mst_edges, + "kruskal": kruskal_mst_edges, + "prim": prim_mst_edges, +} + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data") +def minimum_spanning_edges( + G, algorithm="kruskal", weight="weight", keys=True, data=True, ignore_nan=False +): + """Generate edges in a minimum spanning forest of an undirected + weighted graph. + + A minimum spanning tree is a subgraph of the graph (a tree) + with the minimum sum of edge weights. A spanning forest is a + union of the spanning trees for each connected component of the graph. + + Parameters + ---------- + G : undirected Graph + An undirected graph. If `G` is connected, then the algorithm finds a + spanning tree. Otherwise, a spanning forest is found. + + algorithm : string + The algorithm to use when finding a minimum spanning tree. Valid + choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'. + + weight : string + Edge data key to use for weight (default 'weight'). + + keys : bool + Whether to yield edge key in multigraphs in addition to the edge. + If `G` is not a multigraph, this is ignored. + + data : bool, optional + If True yield the edge data along with the edge. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + Returns + ------- + edges : iterator + An iterator over edges in a maximum spanning tree of `G`. + Edges connecting nodes `u` and `v` are represented as tuples: + `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)` + + If `G` is a multigraph, `keys` indicates whether the edge key `k` will + be reported in the third position in the edge tuple. `data` indicates + whether the edge datadict `d` will appear at the end of the edge tuple. + + If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True + or `(u, v)` if `data` is False. + + Examples + -------- + >>> from networkx.algorithms import tree + + Find minimum spanning edges by Kruskal's algorithm + + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) + >>> mst = tree.minimum_spanning_edges(G, algorithm="kruskal", data=False) + >>> edgelist = list(mst) + >>> sorted(sorted(e) for e in edgelist) + [[0, 1], [1, 2], [2, 3]] + + Find minimum spanning edges by Prim's algorithm + + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) + >>> mst = tree.minimum_spanning_edges(G, algorithm="prim", data=False) + >>> edgelist = list(mst) + >>> sorted(sorted(e) for e in edgelist) + [[0, 1], [1, 2], [2, 3]] + + Notes + ----- + For Borůvka's algorithm, each edge must have a weight attribute, and + each edge weight must be distinct. + + For the other algorithms, if the graph edges do not have a weight + attribute a default weight of 1 will be used. + + Modified code from David Eppstein, April 2006 + http://www.ics.uci.edu/~eppstein/PADS/ + + """ + try: + algo = ALGORITHMS[algorithm] + except KeyError as err: + msg = f"{algorithm} is not a valid choice for an algorithm." + raise ValueError(msg) from err + + return algo( + G, minimum=True, weight=weight, keys=keys, data=data, ignore_nan=ignore_nan + ) + + +@not_implemented_for("directed") +@nx._dispatchable(edge_attrs="weight", preserve_edge_attrs="data") +def maximum_spanning_edges( + G, algorithm="kruskal", weight="weight", keys=True, data=True, ignore_nan=False +): + """Generate edges in a maximum spanning forest of an undirected + weighted graph. + + A maximum spanning tree is a subgraph of the graph (a tree) + with the maximum possible sum of edge weights. A spanning forest is a + union of the spanning trees for each connected component of the graph. + + Parameters + ---------- + G : undirected Graph + An undirected graph. If `G` is connected, then the algorithm finds a + spanning tree. Otherwise, a spanning forest is found. + + algorithm : string + The algorithm to use when finding a maximum spanning tree. Valid + choices are 'kruskal', 'prim', or 'boruvka'. The default is 'kruskal'. + + weight : string + Edge data key to use for weight (default 'weight'). + + keys : bool + Whether to yield edge key in multigraphs in addition to the edge. + If `G` is not a multigraph, this is ignored. + + data : bool, optional + If True yield the edge data along with the edge. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + Returns + ------- + edges : iterator + An iterator over edges in a maximum spanning tree of `G`. + Edges connecting nodes `u` and `v` are represented as tuples: + `(u, v, k, d)` or `(u, v, k)` or `(u, v, d)` or `(u, v)` + + If `G` is a multigraph, `keys` indicates whether the edge key `k` will + be reported in the third position in the edge tuple. `data` indicates + whether the edge datadict `d` will appear at the end of the edge tuple. + + If `G` is not a multigraph, the tuples are `(u, v, d)` if `data` is True + or `(u, v)` if `data` is False. + + Examples + -------- + >>> from networkx.algorithms import tree + + Find maximum spanning edges by Kruskal's algorithm + + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) + >>> mst = tree.maximum_spanning_edges(G, algorithm="kruskal", data=False) + >>> edgelist = list(mst) + >>> sorted(sorted(e) for e in edgelist) + [[0, 1], [0, 3], [1, 2]] + + Find maximum spanning edges by Prim's algorithm + + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) # assign weight 2 to edge 0-3 + >>> mst = tree.maximum_spanning_edges(G, algorithm="prim", data=False) + >>> edgelist = list(mst) + >>> sorted(sorted(e) for e in edgelist) + [[0, 1], [0, 3], [2, 3]] + + Notes + ----- + For Borůvka's algorithm, each edge must have a weight attribute, and + each edge weight must be distinct. + + For the other algorithms, if the graph edges do not have a weight + attribute a default weight of 1 will be used. + + Modified code from David Eppstein, April 2006 + http://www.ics.uci.edu/~eppstein/PADS/ + """ + try: + algo = ALGORITHMS[algorithm] + except KeyError as err: + msg = f"{algorithm} is not a valid choice for an algorithm." + raise ValueError(msg) from err + + return algo( + G, minimum=False, weight=weight, keys=keys, data=data, ignore_nan=ignore_nan + ) + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def minimum_spanning_tree(G, weight="weight", algorithm="kruskal", ignore_nan=False): + """Returns a minimum spanning tree or forest on an undirected graph `G`. + + Parameters + ---------- + G : undirected graph + An undirected graph. If `G` is connected, then the algorithm finds a + spanning tree. Otherwise, a spanning forest is found. + + weight : str + Data key to use for edge weights. + + algorithm : string + The algorithm to use when finding a minimum spanning tree. Valid + choices are 'kruskal', 'prim', or 'boruvka'. The default is + 'kruskal'. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + Returns + ------- + G : NetworkX Graph + A minimum spanning tree or forest. + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) + >>> T = nx.minimum_spanning_tree(G) + >>> sorted(T.edges(data=True)) + [(0, 1, {}), (1, 2, {}), (2, 3, {})] + + + Notes + ----- + For Borůvka's algorithm, each edge must have a weight attribute, and + each edge weight must be distinct. + + For the other algorithms, if the graph edges do not have a weight + attribute a default weight of 1 will be used. + + There may be more than one tree with the same minimum or maximum weight. + See :mod:`networkx.tree.recognition` for more detailed definitions. + + Isolated nodes with self-loops are in the tree as edgeless isolated nodes. + + """ + edges = minimum_spanning_edges( + G, algorithm, weight, keys=True, data=True, ignore_nan=ignore_nan + ) + T = G.__class__() # Same graph class as G + T.graph.update(G.graph) + T.add_nodes_from(G.nodes.items()) + T.add_edges_from(edges) + return T + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def partition_spanning_tree( + G, minimum=True, weight="weight", partition="partition", ignore_nan=False +): + """ + Find a spanning tree while respecting a partition of edges. + + Edges can be flagged as either `INCLUDED` which are required to be in the + returned tree, `EXCLUDED`, which cannot be in the returned tree and `OPEN`. + + This is used in the SpanningTreeIterator to create new partitions following + the algorithm of Sörensen and Janssens [1]_. + + Parameters + ---------- + G : undirected graph + An undirected graph. + + minimum : bool (default: True) + Determines whether the returned tree is the minimum spanning tree of + the partition of the maximum one. + + weight : str + Data key to use for edge weights. + + partition : str + The key for the edge attribute containing the partition + data on the graph. Edges can be included, excluded or open using the + `EdgePartition` enum. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + + Returns + ------- + G : NetworkX Graph + A minimum spanning tree using all of the included edges in the graph and + none of the excluded edges. + + References + ---------- + .. [1] G.K. Janssens, K. Sörensen, An algorithm to generate all spanning + trees in order of increasing cost, Pesquisa Operacional, 2005-08, + Vol. 25 (2), p. 219-229, + https://www.scielo.br/j/pope/a/XHswBwRwJyrfL88dmMwYNWp/?lang=en + """ + edges = kruskal_mst_edges( + G, + minimum, + weight, + keys=True, + data=True, + ignore_nan=ignore_nan, + partition=partition, + ) + T = G.__class__() # Same graph class as G + T.graph.update(G.graph) + T.add_nodes_from(G.nodes.items()) + T.add_edges_from(edges) + return T + + +@nx._dispatchable(preserve_all_attrs=True, returns_graph=True) +def maximum_spanning_tree(G, weight="weight", algorithm="kruskal", ignore_nan=False): + """Returns a maximum spanning tree or forest on an undirected graph `G`. + + Parameters + ---------- + G : undirected graph + An undirected graph. If `G` is connected, then the algorithm finds a + spanning tree. Otherwise, a spanning forest is found. + + weight : str + Data key to use for edge weights. + + algorithm : string + The algorithm to use when finding a maximum spanning tree. Valid + choices are 'kruskal', 'prim', or 'boruvka'. The default is + 'kruskal'. + + ignore_nan : bool (default: False) + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + + + Returns + ------- + G : NetworkX Graph + A maximum spanning tree or forest. + + + Examples + -------- + >>> G = nx.cycle_graph(4) + >>> G.add_edge(0, 3, weight=2) + >>> T = nx.maximum_spanning_tree(G) + >>> sorted(T.edges(data=True)) + [(0, 1, {}), (0, 3, {'weight': 2}), (1, 2, {})] + + + Notes + ----- + For Borůvka's algorithm, each edge must have a weight attribute, and + each edge weight must be distinct. + + For the other algorithms, if the graph edges do not have a weight + attribute a default weight of 1 will be used. + + There may be more than one tree with the same minimum or maximum weight. + See :mod:`networkx.tree.recognition` for more detailed definitions. + + Isolated nodes with self-loops are in the tree as edgeless isolated nodes. + + """ + edges = maximum_spanning_edges( + G, algorithm, weight, keys=True, data=True, ignore_nan=ignore_nan + ) + edges = list(edges) + T = G.__class__() # Same graph class as G + T.graph.update(G.graph) + T.add_nodes_from(G.nodes.items()) + T.add_edges_from(edges) + return T + + +@py_random_state(3) +@nx._dispatchable(preserve_edge_attrs=True, returns_graph=True) +def random_spanning_tree(G, weight=None, *, multiplicative=True, seed=None): + """ + Sample a random spanning tree using the edges weights of `G`. + + This function supports two different methods for determining the + probability of the graph. If ``multiplicative=True``, the probability + is based on the product of edge weights, and if ``multiplicative=False`` + it is based on the sum of the edge weight. However, since it is + easier to determine the total weight of all spanning trees for the + multiplicative version, that is significantly faster and should be used if + possible. Additionally, setting `weight` to `None` will cause a spanning tree + to be selected with uniform probability. + + The function uses algorithm A8 in [1]_ . + + Parameters + ---------- + G : nx.Graph + An undirected version of the original graph. + + weight : string + The edge key for the edge attribute holding edge weight. + + multiplicative : bool, default=True + If `True`, the probability of each tree is the product of its edge weight + over the sum of the product of all the spanning trees in the graph. If + `False`, the probability is the sum of its edge weight over the sum of + the sum of weights for all spanning trees in the graph. + + seed : integer, random_state, or None (default) + Indicator of random number generation state. + See :ref:`Randomness`. + + Returns + ------- + nx.Graph + A spanning tree using the distribution defined by the weight of the tree. + + References + ---------- + .. [1] V. Kulkarni, Generating random combinatorial objects, Journal of + Algorithms, 11 (1990), pp. 185–207 + """ + + def find_node(merged_nodes, node): + """ + We can think of clusters of contracted nodes as having one + representative in the graph. Each node which is not in merged_nodes + is still its own representative. Since a representative can be later + contracted, we need to recursively search though the dict to find + the final representative, but once we know it we can use path + compression to speed up the access of the representative for next time. + + This cannot be replaced by the standard NetworkX union_find since that + data structure will merge nodes with less representing nodes into the + one with more representing nodes but this function requires we merge + them using the order that contract_edges contracts using. + + Parameters + ---------- + merged_nodes : dict + The dict storing the mapping from node to representative + node + The node whose representative we seek + + Returns + ------- + The representative of the `node` + """ + if node not in merged_nodes: + return node + else: + rep = find_node(merged_nodes, merged_nodes[node]) + merged_nodes[node] = rep + return rep + + def prepare_graph(): + """ + For the graph `G`, remove all edges not in the set `V` and then + contract all edges in the set `U`. + + Returns + ------- + A copy of `G` which has had all edges not in `V` removed and all edges + in `U` contracted. + """ + + # The result is a MultiGraph version of G so that parallel edges are + # allowed during edge contraction + result = nx.MultiGraph(incoming_graph_data=G) + + # Remove all edges not in V + edges_to_remove = set(result.edges()).difference(V) + result.remove_edges_from(edges_to_remove) + + # Contract all edges in U + # + # Imagine that you have two edges to contract and they share an + # endpoint like this: + # [0] ----- [1] ----- [2] + # If we contract (0, 1) first, the contraction function will always + # delete the second node it is passed so the resulting graph would be + # [0] ----- [2] + # and edge (1, 2) no longer exists but (0, 2) would need to be contracted + # in its place now. That is why I use the below dict as a merge-find + # data structure with path compression to track how the nodes are merged. + merged_nodes = {} + + for u, v in U: + u_rep = find_node(merged_nodes, u) + v_rep = find_node(merged_nodes, v) + # We cannot contract a node with itself + if u_rep == v_rep: + continue + nx.contracted_nodes(result, u_rep, v_rep, self_loops=False, copy=False) + merged_nodes[v_rep] = u_rep + + return merged_nodes, result + + def spanning_tree_total_weight(G, weight): + """ + Find the sum of weights of the spanning trees of `G` using the + appropriate `method`. + + This is easy if the chosen method is 'multiplicative', since we can + use Kirchhoff's Tree Matrix Theorem directly. However, with the + 'additive' method, this process is slightly more complex and less + computationally efficient as we have to find the number of spanning + trees which contain each possible edge in the graph. + + Parameters + ---------- + G : NetworkX Graph + The graph to find the total weight of all spanning trees on. + + weight : string + The key for the weight edge attribute of the graph. + + Returns + ------- + float + The sum of either the multiplicative or additive weight for all + spanning trees in the graph. + """ + if multiplicative: + return number_of_spanning_trees(G, weight=weight) + else: + # There are two cases for the total spanning tree additive weight. + # 1. There is one edge in the graph. Then the only spanning tree is + # that edge itself, which will have a total weight of that edge + # itself. + if G.number_of_edges() == 1: + return G.edges(data=weight).__iter__().__next__()[2] + # 2. There are no edges or two or more edges in the graph. Then, we find the + # total weight of the spanning trees using the formula in the + # reference paper: take the weight of each edge and multiply it by + # the number of spanning trees which include that edge. This + # can be accomplished by contracting the edge and finding the + # multiplicative total spanning tree weight if the weight of each edge + # is assumed to be 1, which is conveniently built into networkx already, + # by calling number_of_spanning_trees with weight=None. + # Note that with no edges the returned value is just zero. + else: + total = 0 + for u, v, w in G.edges(data=weight): + total += w * nx.number_of_spanning_trees( + nx.contracted_edge(G, edge=(u, v), self_loops=False), + weight=None, + ) + return total + + if G.number_of_nodes() < 2: + # no edges in the spanning tree + return nx.empty_graph(G.nodes) + + U = set() + st_cached_value = 0 + V = set(G.edges()) + shuffled_edges = list(G.edges()) + seed.shuffle(shuffled_edges) + + for u, v in shuffled_edges: + e_weight = G[u][v][weight] if weight is not None else 1 + node_map, prepared_G = prepare_graph() + G_total_tree_weight = spanning_tree_total_weight(prepared_G, weight) + # Add the edge to U so that we can compute the total tree weight + # assuming we include that edge + # Now, if (u, v) cannot exist in G because it is fully contracted out + # of existence, then it by definition cannot influence G_e's Kirchhoff + # value. But, we also cannot pick it. + rep_edge = (find_node(node_map, u), find_node(node_map, v)) + # Check to see if the 'representative edge' for the current edge is + # in prepared_G. If so, then we can pick it. + if rep_edge in prepared_G.edges: + prepared_G_e = nx.contracted_edge( + prepared_G, edge=rep_edge, self_loops=False + ) + G_e_total_tree_weight = spanning_tree_total_weight(prepared_G_e, weight) + if multiplicative: + threshold = e_weight * G_e_total_tree_weight / G_total_tree_weight + else: + numerator = (st_cached_value + e_weight) * nx.number_of_spanning_trees( + prepared_G_e + ) + G_e_total_tree_weight + denominator = ( + st_cached_value * nx.number_of_spanning_trees(prepared_G) + + G_total_tree_weight + ) + threshold = numerator / denominator + else: + threshold = 0.0 + z = seed.uniform(0.0, 1.0) + if z > threshold: + # Remove the edge from V since we did not pick it. + V.remove((u, v)) + else: + # Add the edge to U since we picked it. + st_cached_value += e_weight + U.add((u, v)) + # If we decide to keep an edge, it may complete the spanning tree. + if len(U) == G.number_of_nodes() - 1: + spanning_tree = nx.Graph() + spanning_tree.add_edges_from(U) + return spanning_tree + raise Exception(f"Something went wrong! Only {len(U)} edges in the spanning tree!") + + +class SpanningTreeIterator: + """ + Iterate over all spanning trees of a graph in either increasing or + decreasing cost. + + Notes + ----- + This iterator uses the partition scheme from [1]_ (included edges, + excluded edges and open edges) as well as a modified Kruskal's Algorithm + to generate minimum spanning trees which respect the partition of edges. + For spanning trees with the same weight, ties are broken arbitrarily. + + References + ---------- + .. [1] G.K. Janssens, K. Sörensen, An algorithm to generate all spanning + trees in order of increasing cost, Pesquisa Operacional, 2005-08, + Vol. 25 (2), p. 219-229, + https://www.scielo.br/j/pope/a/XHswBwRwJyrfL88dmMwYNWp/?lang=en + """ + + @dataclass(order=True) + class Partition: + """ + This dataclass represents a partition and stores a dict with the edge + data and the weight of the minimum spanning tree of the partition dict. + """ + + mst_weight: float + partition_dict: dict = field(compare=False) + + def __copy__(self): + return SpanningTreeIterator.Partition( + self.mst_weight, self.partition_dict.copy() + ) + + def __init__(self, G, weight="weight", minimum=True, ignore_nan=False): + """ + Initialize the iterator + + Parameters + ---------- + G : nx.Graph + The directed graph which we need to iterate trees over + + weight : String, default = "weight" + The edge attribute used to store the weight of the edge + + minimum : bool, default = True + Return the trees in increasing order while true and decreasing order + while false. + + ignore_nan : bool, default = False + If a NaN is found as an edge weight normally an exception is raised. + If `ignore_nan is True` then that edge is ignored instead. + """ + self.G = G.copy() + self.G.__networkx_cache__ = None # Disable caching + self.weight = weight + self.minimum = minimum + self.ignore_nan = ignore_nan + # Randomly create a key for an edge attribute to hold the partition data + self.partition_key = ( + "SpanningTreeIterators super secret partition attribute name" + ) + + def __iter__(self): + """ + Returns + ------- + SpanningTreeIterator + The iterator object for this graph + """ + self.partition_queue = PriorityQueue() + self._clear_partition(self.G) + mst_weight = partition_spanning_tree( + self.G, self.minimum, self.weight, self.partition_key, self.ignore_nan + ).size(weight=self.weight) + + self.partition_queue.put( + self.Partition(mst_weight if self.minimum else -mst_weight, {}) + ) + + return self + + def __next__(self): + """ + Returns + ------- + (multi)Graph + The spanning tree of next greatest weight, which ties broken + arbitrarily. + """ + if self.partition_queue.empty(): + del self.G, self.partition_queue + raise StopIteration + + partition = self.partition_queue.get() + self._write_partition(partition) + next_tree = partition_spanning_tree( + self.G, self.minimum, self.weight, self.partition_key, self.ignore_nan + ) + self._partition(partition, next_tree) + + self._clear_partition(next_tree) + return next_tree + + def _partition(self, partition, partition_tree): + """ + Create new partitions based of the minimum spanning tree of the + current minimum partition. + + Parameters + ---------- + partition : Partition + The Partition instance used to generate the current minimum spanning + tree. + partition_tree : nx.Graph + The minimum spanning tree of the input partition. + """ + # create two new partitions with the data from the input partition dict + p1 = self.Partition(0, partition.partition_dict.copy()) + p2 = self.Partition(0, partition.partition_dict.copy()) + for e in partition_tree.edges: + # determine if the edge was open or included + if e not in partition.partition_dict: + # This is an open edge + p1.partition_dict[e] = EdgePartition.EXCLUDED + p2.partition_dict[e] = EdgePartition.INCLUDED + + self._write_partition(p1) + p1_mst = partition_spanning_tree( + self.G, + self.minimum, + self.weight, + self.partition_key, + self.ignore_nan, + ) + p1_mst_weight = p1_mst.size(weight=self.weight) + if nx.is_connected(p1_mst): + p1.mst_weight = p1_mst_weight if self.minimum else -p1_mst_weight + self.partition_queue.put(p1.__copy__()) + p1.partition_dict = p2.partition_dict.copy() + + def _write_partition(self, partition): + """ + Writes the desired partition into the graph to calculate the minimum + spanning tree. + + Parameters + ---------- + partition : Partition + A Partition dataclass describing a partition on the edges of the + graph. + """ + + partition_dict = partition.partition_dict + partition_key = self.partition_key + G = self.G + + edges = ( + G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) + ) + for *e, d in edges: + d[partition_key] = partition_dict.get(tuple(e), EdgePartition.OPEN) + + def _clear_partition(self, G): + """ + Removes partition data from the graph + """ + partition_key = self.partition_key + edges = ( + G.edges(keys=True, data=True) if G.is_multigraph() else G.edges(data=True) + ) + for *e, d in edges: + if partition_key in d: + del d[partition_key] + + +@nx._dispatchable(edge_attrs="weight") +def number_of_spanning_trees(G, *, root=None, weight=None): + """Returns the number of spanning trees in `G`. + + A spanning tree for an undirected graph is a tree that connects + all nodes in the graph. For a directed graph, the analog of a + spanning tree is called a (spanning) arborescence. The arborescence + includes a unique directed path from the `root` node to each other node. + The graph must be weakly connected, and the root must be a node + that includes all nodes as successors [3]_. Note that to avoid + discussing sink-roots and reverse-arborescences, we have reversed + the edge orientation from [3]_ and use the in-degree laplacian. + + This function (when `weight` is `None`) returns the number of + spanning trees for an undirected graph and the number of + arborescences from a single root node for a directed graph. + When `weight` is the name of an edge attribute which holds the + weight value of each edge, the function returns the sum over + all trees of the multiplicative weight of each tree. That is, + the weight of the tree is the product of its edge weights. + + Kirchoff's Tree Matrix Theorem states that any cofactor of the + Laplacian matrix of a graph is the number of spanning trees in the + graph. (Here we use cofactors for a diagonal entry so that the + cofactor becomes the determinant of the matrix with one row + and its matching column removed.) For a weighted Laplacian matrix, + the cofactor is the sum across all spanning trees of the + multiplicative weight of each tree. That is, the weight of each + tree is the product of its edge weights. The theorem is also + known as Kirchhoff's theorem [1]_ and the Matrix-Tree theorem [2]_. + + For directed graphs, a similar theorem (Tutte's Theorem) holds with + the cofactor chosen to be the one with row and column removed that + correspond to the root. The cofactor is the number of arborescences + with the specified node as root. And the weighted version gives the + sum of the arborescence weights with root `root`. The arborescence + weight is the product of its edge weights. + + Parameters + ---------- + G : NetworkX graph + + root : node + A node in the directed graph `G` that has all nodes as descendants. + (This is ignored for undirected graphs.) + + weight : string or None, optional (default=None) + The name of the edge attribute holding the edge weight. + If `None`, then each edge is assumed to have a weight of 1. + + Returns + ------- + Number + Undirected graphs: + The number of spanning trees of the graph `G`. + Or the sum of all spanning tree weights of the graph `G` + where the weight of a tree is the product of its edge weights. + Directed graphs: + The number of arborescences of `G` rooted at node `root`. + Or the sum of all arborescence weights of the graph `G` with + specified root where the weight of an arborescence is the product + of its edge weights. + + Raises + ------ + NetworkXPointlessConcept + If `G` does not contain any nodes. + + NetworkXError + If the graph `G` is directed and the root node + is not specified or is not in G. + + Examples + -------- + >>> G = nx.complete_graph(5) + >>> round(nx.number_of_spanning_trees(G)) + 125 + + >>> G = nx.Graph() + >>> G.add_edge(1, 2, weight=2) + >>> G.add_edge(1, 3, weight=1) + >>> G.add_edge(2, 3, weight=1) + >>> round(nx.number_of_spanning_trees(G, weight="weight")) + 5 + + Notes + ----- + Self-loops are excluded. Multi-edges are contracted in one edge + equal to the sum of the weights. + + References + ---------- + .. [1] Wikipedia + "Kirchhoff's theorem." + https://en.wikipedia.org/wiki/Kirchhoff%27s_theorem + .. [2] Kirchhoff, G. R. + Über die Auflösung der Gleichungen, auf welche man + bei der Untersuchung der linearen Vertheilung + Galvanischer Ströme geführt wird + Annalen der Physik und Chemie, vol. 72, pp. 497-508, 1847. + .. [3] Margoliash, J. + "Matrix-Tree Theorem for Directed Graphs" + https://www.math.uchicago.edu/~may/VIGRE/VIGRE2010/REUPapers/Margoliash.pdf + """ + import numpy as np + + if len(G) == 0: + raise nx.NetworkXPointlessConcept("Graph G must contain at least one node.") + + # undirected G + if not nx.is_directed(G): + if not nx.is_connected(G): + return 0 + G_laplacian = nx.laplacian_matrix(G, weight=weight).toarray() + return float(np.linalg.det(G_laplacian[1:, 1:])) + + # directed G + if root is None: + raise nx.NetworkXError("Input `root` must be provided when G is directed") + if root not in G: + raise nx.NetworkXError("The node root is not in the graph G.") + if not nx.is_weakly_connected(G): + return 0 + + # Compute directed Laplacian matrix + nodelist = [root] + [n for n in G if n != root] + A = nx.adjacency_matrix(G, nodelist=nodelist, weight=weight) + D = np.diag(A.sum(axis=0)) + G_laplacian = D - A + + # Compute number of spanning trees + return float(np.linalg.det(G_laplacian[1:, 1:])) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/operations.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/operations.py new file mode 100644 index 0000000000000000000000000000000000000000..a75770a5968eaff5f5975d23a6af4557b52e6e7e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/operations.py @@ -0,0 +1,106 @@ +"""Operations on trees.""" + +from functools import partial +from itertools import accumulate, chain + +import networkx as nx + +__all__ = ["join_trees"] + + +# Argument types don't match dispatching, but allow manual selection of backend +@nx._dispatchable(graphs=None, returns_graph=True) +def join_trees(rooted_trees, *, label_attribute=None, first_label=0): + """Returns a new rooted tree made by joining `rooted_trees` + + Constructs a new tree by joining each tree in `rooted_trees`. + A new root node is added and connected to each of the roots + of the input trees. While copying the nodes from the trees, + relabeling to integers occurs. If the `label_attribute` is provided, + the old node labels will be stored in the new tree under this attribute. + + Parameters + ---------- + rooted_trees : list + A list of pairs in which each left element is a NetworkX graph + object representing a tree and each right element is the root + node of that tree. The nodes of these trees will be relabeled to + integers. + + label_attribute : str + If provided, the old node labels will be stored in the new tree + under this node attribute. If not provided, the original labels + of the nodes in the input trees are not stored. + + first_label : int, optional (default=0) + Specifies the label for the new root node. If provided, the root node of the joined tree + will have this label. If not provided, the root node will default to a label of 0. + + Returns + ------- + NetworkX graph + The rooted tree resulting from joining the provided `rooted_trees`. The new tree has a root node + labeled as specified by `first_label` (defaulting to 0 if not provided). Subtrees from the input + `rooted_trees` are attached to this new root node. Each non-root node, if the `label_attribute` + is provided, has an attribute that indicates the original label of the node in the input tree. + + Notes + ----- + Trees are stored in NetworkX as NetworkX Graphs. There is no specific + enforcement of the fact that these are trees. Testing for each tree + can be done using :func:`networkx.is_tree`. + + Graph, edge, and node attributes are propagated from the given + rooted trees to the created tree. If there are any overlapping graph + attributes, those from later trees will overwrite those from earlier + trees in the tuple of positional arguments. + + Examples + -------- + Join two full balanced binary trees of height *h* to get a full + balanced binary tree of depth *h* + 1:: + + >>> h = 4 + >>> left = nx.balanced_tree(2, h) + >>> right = nx.balanced_tree(2, h) + >>> joined_tree = nx.join_trees([(left, 0), (right, 0)]) + >>> nx.is_isomorphic(joined_tree, nx.balanced_tree(2, h + 1)) + True + + """ + if not rooted_trees: + return nx.empty_graph(1) + + # Unzip the zipped list of (tree, root) pairs. + trees, roots = zip(*rooted_trees) + + # The join of the trees has the same type as the type of the first tree. + R = type(trees[0])() + + lengths = (len(tree) for tree in trees[:-1]) + first_labels = list(accumulate(lengths, initial=first_label + 1)) + + new_roots = [] + for tree, root, first_node in zip(trees, roots, first_labels): + new_root = first_node + list(tree.nodes()).index(root) + new_roots.append(new_root) + + # Relabel the nodes so that their union is the integers starting at first_label. + relabel = partial( + nx.convert_node_labels_to_integers, label_attribute=label_attribute + ) + new_trees = [ + relabel(tree, first_label=first_label) + for tree, first_label in zip(trees, first_labels) + ] + + # Add all sets of nodes and edges, attributes + for tree in new_trees: + R.update(tree) + + # Finally, join the subtrees at the root. We know first_label is unused by + # the way we relabeled the subtrees. + R.add_node(first_label) + R.add_edges_from((first_label, root) for root in new_roots) + + return R diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py new file mode 100644 index 0000000000000000000000000000000000000000..a9eae98707a6889213ff8b93887c481ba59215a0 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/recognition.py @@ -0,0 +1,273 @@ +""" +Recognition Tests +================= + +A *forest* is an acyclic, undirected graph, and a *tree* is a connected forest. +Depending on the subfield, there are various conventions for generalizing these +definitions to directed graphs. + +In one convention, directed variants of forest and tree are defined in an +identical manner, except that the direction of the edges is ignored. In effect, +each directed edge is treated as a single undirected edge. Then, additional +restrictions are imposed to define *branchings* and *arborescences*. + +In another convention, directed variants of forest and tree correspond to +the previous convention's branchings and arborescences, respectively. Then two +new terms, *polyforest* and *polytree*, are defined to correspond to the other +convention's forest and tree. + +Summarizing:: + + +-----------------------------+ + | Convention A | Convention B | + +=============================+ + | forest | polyforest | + | tree | polytree | + | branching | forest | + | arborescence | tree | + +-----------------------------+ + +Each convention has its reasons. The first convention emphasizes definitional +similarity in that directed forests and trees are only concerned with +acyclicity and do not have an in-degree constraint, just as their undirected +counterparts do not. The second convention emphasizes functional similarity +in the sense that the directed analog of a spanning tree is a spanning +arborescence. That is, take any spanning tree and choose one node as the root. +Then every edge is assigned a direction such there is a directed path from the +root to every other node. The result is a spanning arborescence. + +NetworkX follows convention "A". Explicitly, these are: + +undirected forest + An undirected graph with no undirected cycles. + +undirected tree + A connected, undirected forest. + +directed forest + A directed graph with no undirected cycles. Equivalently, the underlying + graph structure (which ignores edge orientations) is an undirected forest. + In convention B, this is known as a polyforest. + +directed tree + A weakly connected, directed forest. Equivalently, the underlying graph + structure (which ignores edge orientations) is an undirected tree. In + convention B, this is known as a polytree. + +branching + A directed forest with each node having, at most, one parent. So the maximum + in-degree is equal to 1. In convention B, this is known as a forest. + +arborescence + A directed tree with each node having, at most, one parent. So the maximum + in-degree is equal to 1. In convention B, this is known as a tree. + +For trees and arborescences, the adjective "spanning" may be added to designate +that the graph, when considered as a forest/branching, consists of a single +tree/arborescence that includes all nodes in the graph. It is true, by +definition, that every tree/arborescence is spanning with respect to the nodes +that define the tree/arborescence and so, it might seem redundant to introduce +the notion of "spanning". However, the nodes may represent a subset of +nodes from a larger graph, and it is in this context that the term "spanning" +becomes a useful notion. + +""" + +import networkx as nx + +__all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"] + + +@nx.utils.not_implemented_for("undirected") +@nx._dispatchable +def is_arborescence(G): + """ + Returns True if `G` is an arborescence. + + An arborescence is a directed tree with maximum in-degree equal to 1. + + Parameters + ---------- + G : graph + The graph to test. + + Returns + ------- + b : bool + A boolean that is True if `G` is an arborescence. + + Examples + -------- + >>> G = nx.DiGraph([(0, 1), (0, 2), (2, 3), (3, 4)]) + >>> nx.is_arborescence(G) + True + >>> G.remove_edge(0, 1) + >>> G.add_edge(1, 2) # maximum in-degree is 2 + >>> nx.is_arborescence(G) + False + + Notes + ----- + In another convention, an arborescence is known as a *tree*. + + See Also + -------- + is_tree + + """ + return is_tree(G) and max(d for n, d in G.in_degree()) <= 1 + + +@nx.utils.not_implemented_for("undirected") +@nx._dispatchable +def is_branching(G): + """ + Returns True if `G` is a branching. + + A branching is a directed forest with maximum in-degree equal to 1. + + Parameters + ---------- + G : directed graph + The directed graph to test. + + Returns + ------- + b : bool + A boolean that is True if `G` is a branching. + + Examples + -------- + >>> G = nx.DiGraph([(0, 1), (1, 2), (2, 3), (3, 4)]) + >>> nx.is_branching(G) + True + >>> G.remove_edge(2, 3) + >>> G.add_edge(3, 1) # maximum in-degree is 2 + >>> nx.is_branching(G) + False + + Notes + ----- + In another convention, a branching is also known as a *forest*. + + See Also + -------- + is_forest + + """ + return is_forest(G) and max(d for n, d in G.in_degree()) <= 1 + + +@nx._dispatchable +def is_forest(G): + """ + Returns True if `G` is a forest. + + A forest is a graph with no undirected cycles. + + For directed graphs, `G` is a forest if the underlying graph is a forest. + The underlying graph is obtained by treating each directed edge as a single + undirected edge in a multigraph. + + Parameters + ---------- + G : graph + The graph to test. + + Returns + ------- + b : bool + A boolean that is True if `G` is a forest. + + Raises + ------ + NetworkXPointlessConcept + If `G` is empty. + + Examples + -------- + >>> G = nx.Graph() + >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)]) + >>> nx.is_forest(G) + True + >>> G.add_edge(4, 1) + >>> nx.is_forest(G) + False + + Notes + ----- + In another convention, a directed forest is known as a *polyforest* and + then *forest* corresponds to a *branching*. + + See Also + -------- + is_branching + + """ + if len(G) == 0: + raise nx.exception.NetworkXPointlessConcept("G has no nodes.") + + if G.is_directed(): + components = (G.subgraph(c) for c in nx.weakly_connected_components(G)) + else: + components = (G.subgraph(c) for c in nx.connected_components(G)) + + return all(len(c) - 1 == c.number_of_edges() for c in components) + + +@nx._dispatchable +def is_tree(G): + """ + Returns True if `G` is a tree. + + A tree is a connected graph with no undirected cycles. + + For directed graphs, `G` is a tree if the underlying graph is a tree. The + underlying graph is obtained by treating each directed edge as a single + undirected edge in a multigraph. + + Parameters + ---------- + G : graph + The graph to test. + + Returns + ------- + b : bool + A boolean that is True if `G` is a tree. + + Raises + ------ + NetworkXPointlessConcept + If `G` is empty. + + Examples + -------- + >>> G = nx.Graph() + >>> G.add_edges_from([(1, 2), (1, 3), (2, 4), (2, 5)]) + >>> nx.is_tree(G) # n-1 edges + True + >>> G.add_edge(3, 4) + >>> nx.is_tree(G) # n edges + False + + Notes + ----- + In another convention, a directed tree is known as a *polytree* and then + *tree* corresponds to an *arborescence*. + + See Also + -------- + is_arborescence + + """ + if len(G) == 0: + raise nx.exception.NetworkXPointlessConcept("G has no nodes.") + + if G.is_directed(): + is_connected = nx.is_weakly_connected + else: + is_connected = nx.is_connected + + # A connected graph with no cycles has n-1 edges. + return len(G) - 1 == G.number_of_edges() and is_connected(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e1a6cda335c3b69f63d7791c798ca43ae9c7eb9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_branchings.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_branchings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7a2b5fe39778a1433d6e1eea0935778b05fb831 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_branchings.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_coding.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_coding.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c033a7cbf8002b24092cc89a06c1c1e39a2e5acc Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_coding.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_decomposition.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_decomposition.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3eb9aa2c27d46e71b743ebfbe4e37b26b37b5d53 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_decomposition.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_distance_measures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_distance_measures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fc6eb628041373de29e5b79624c9808cb34355d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_distance_measures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_mst.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_mst.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c597631b2a12ea8105fbc2be80e884c3ac06724 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_mst.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_operations.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_operations.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fc09182a314a95afa63f0ebd308767c11452c58 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_operations.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_recognition.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_recognition.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c24a6273e0ede4ebe3a9029dad1fd56fd15f5530 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/__pycache__/test_recognition.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_branchings.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_branchings.py new file mode 100644 index 0000000000000000000000000000000000000000..9be976abdeb0298f576b1c72208d4379aea56c9d --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_branchings.py @@ -0,0 +1,624 @@ +import math +from operator import itemgetter + +import pytest + +import networkx as nx +from networkx.algorithms.tree import branchings, recognition + +np = pytest.importorskip("numpy") + +# +# Explicitly discussed examples from Edmonds paper. +# + +# Used in Figures A-F. +# +# fmt: off +G_array = np.array([ + # 0 1 2 3 4 5 6 7 8 + [0, 0, 12, 0, 12, 0, 0, 0, 0], # 0 + [4, 0, 0, 0, 0, 13, 0, 0, 0], # 1 + [0, 17, 0, 21, 0, 12, 0, 0, 0], # 2 + [5, 0, 0, 0, 17, 0, 18, 0, 0], # 3 + [0, 0, 0, 0, 0, 0, 0, 12, 0], # 4 + [0, 0, 0, 0, 0, 0, 14, 0, 12], # 5 + [0, 0, 21, 0, 0, 0, 0, 0, 15], # 6 + [0, 0, 0, 19, 0, 0, 15, 0, 0], # 7 + [0, 0, 0, 0, 0, 0, 0, 18, 0], # 8 +], dtype=int) + +# Two copies of the graph from the original paper as disconnected components +G_big_array = np.zeros(np.array(G_array.shape) * 2, dtype=int) +G_big_array[:G_array.shape[0], :G_array.shape[1]] = G_array +G_big_array[G_array.shape[0]:, G_array.shape[1]:] = G_array + +# fmt: on + + +def G1(): + G = nx.from_numpy_array(G_array, create_using=nx.MultiDiGraph) + return G + + +def G2(): + # Now we shift all the weights by -10. + # Should not affect optimal arborescence, but does affect optimal branching. + Garr = G_array.copy() + Garr[np.nonzero(Garr)] -= 10 + G = nx.from_numpy_array(Garr, create_using=nx.MultiDiGraph) + return G + + +# An optimal branching for G1 that is also a spanning arborescence. So it is +# also an optimal spanning arborescence. +# +optimal_arborescence_1 = [ + (0, 2, 12), + (2, 1, 17), + (2, 3, 21), + (1, 5, 13), + (3, 4, 17), + (3, 6, 18), + (6, 8, 15), + (8, 7, 18), +] + +# For G2, the optimal branching of G1 (with shifted weights) is no longer +# an optimal branching, but it is still an optimal spanning arborescence +# (just with shifted weights). An optimal branching for G2 is similar to what +# appears in figure G (this is greedy_subopt_branching_1a below), but with the +# edge (3, 0, 5), which is now (3, 0, -5), removed. Thus, the optimal branching +# is not a spanning arborescence. The code finds optimal_branching_2a. +# An alternative and equivalent branching is optimal_branching_2b. We would +# need to modify the code to iterate through all equivalent optimal branchings. +# +# These are maximal branchings or arborescences. +optimal_branching_2a = [ + (5, 6, 4), + (6, 2, 11), + (6, 8, 5), + (8, 7, 8), + (2, 1, 7), + (2, 3, 11), + (3, 4, 7), +] +optimal_branching_2b = [ + (8, 7, 8), + (7, 3, 9), + (3, 4, 7), + (3, 6, 8), + (6, 2, 11), + (2, 1, 7), + (1, 5, 3), +] +optimal_arborescence_2 = [ + (0, 2, 2), + (2, 1, 7), + (2, 3, 11), + (1, 5, 3), + (3, 4, 7), + (3, 6, 8), + (6, 8, 5), + (8, 7, 8), +] + +# Two suboptimal maximal branchings on G1 obtained from a greedy algorithm. +# 1a matches what is shown in Figure G in Edmonds's paper. +greedy_subopt_branching_1a = [ + (5, 6, 14), + (6, 2, 21), + (6, 8, 15), + (8, 7, 18), + (2, 1, 17), + (2, 3, 21), + (3, 0, 5), + (3, 4, 17), +] +greedy_subopt_branching_1b = [ + (8, 7, 18), + (7, 6, 15), + (6, 2, 21), + (2, 1, 17), + (2, 3, 21), + (1, 5, 13), + (3, 0, 5), + (3, 4, 17), +] + + +def build_branching(edges, double=False): + G = nx.DiGraph() + for u, v, weight in edges: + G.add_edge(u, v, weight=weight) + if double: + G.add_edge(u + 9, v + 9, weight=weight) + return G + + +def sorted_edges(G, attr="weight", default=1): + edges = [(u, v, data.get(attr, default)) for (u, v, data) in G.edges(data=True)] + edges = sorted(edges, key=lambda x: (x[2], x[1], x[0])) + return edges + + +def assert_equal_branchings(G1, G2, attr="weight", default=1): + edges1 = list(G1.edges(data=True)) + edges2 = list(G2.edges(data=True)) + assert len(edges1) == len(edges2) + + # Grab the weights only. + e1 = sorted_edges(G1, attr, default) + e2 = sorted_edges(G2, attr, default) + + for a, b in zip(e1, e2): + assert a[:2] == b[:2] + np.testing.assert_almost_equal(a[2], b[2]) + + +################ + + +def test_optimal_branching1(): + G = build_branching(optimal_arborescence_1) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 131 + + +def test_optimal_branching2a(): + G = build_branching(optimal_branching_2a) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 53 + + +def test_optimal_branching2b(): + G = build_branching(optimal_branching_2b) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 53 + + +def test_optimal_arborescence2(): + G = build_branching(optimal_arborescence_2) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 51 + + +def test_greedy_suboptimal_branching1a(): + G = build_branching(greedy_subopt_branching_1a) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 128 + + +def test_greedy_suboptimal_branching1b(): + G = build_branching(greedy_subopt_branching_1b) + assert recognition.is_arborescence(G), True + assert branchings.branching_weight(G) == 127 + + +def test_greedy_max1(): + # Standard test. + # + G = G1() + B = branchings.greedy_branching(G) + # There are only two possible greedy branchings. The sorting is such + # that it should equal the second suboptimal branching: 1b. + B_ = build_branching(greedy_subopt_branching_1b) + assert_equal_branchings(B, B_) + + +def test_greedy_branching_kwarg_kind(): + G = G1() + with pytest.raises(nx.NetworkXException, match="Unknown value for `kind`."): + B = branchings.greedy_branching(G, kind="lol") + + +def test_greedy_branching_for_unsortable_nodes(): + G = nx.DiGraph() + G.add_weighted_edges_from([((2, 3), 5, 1), (3, "a", 1), (2, 4, 5)]) + edges = [(u, v, data.get("weight", 1)) for (u, v, data) in G.edges(data=True)] + with pytest.raises(TypeError): + edges.sort(key=itemgetter(2, 0, 1), reverse=True) + B = branchings.greedy_branching(G, kind="max").edges(data=True) + assert list(B) == [ + ((2, 3), 5, {"weight": 1}), + (3, "a", {"weight": 1}), + (2, 4, {"weight": 5}), + ] + + +def test_greedy_max2(): + # Different default weight. + # + G = G1() + del G[1][0][0]["weight"] + B = branchings.greedy_branching(G, default=6) + # Chosen so that edge (3,0,5) is not selected and (1,0,6) is instead. + + edges = [ + (1, 0, 6), + (1, 5, 13), + (7, 6, 15), + (2, 1, 17), + (3, 4, 17), + (8, 7, 18), + (2, 3, 21), + (6, 2, 21), + ] + B_ = build_branching(edges) + assert_equal_branchings(B, B_) + + +def test_greedy_max3(): + # All equal weights. + # + G = G1() + B = branchings.greedy_branching(G, attr=None) + + # This is mostly arbitrary...the output was generated by running the algo. + edges = [ + (2, 1, 1), + (3, 0, 1), + (3, 4, 1), + (5, 8, 1), + (6, 2, 1), + (7, 3, 1), + (7, 6, 1), + (8, 7, 1), + ] + B_ = build_branching(edges) + assert_equal_branchings(B, B_, default=1) + + +def test_greedy_min(): + G = G1() + B = branchings.greedy_branching(G, kind="min") + + edges = [ + (1, 0, 4), + (0, 2, 12), + (0, 4, 12), + (2, 5, 12), + (4, 7, 12), + (5, 8, 12), + (5, 6, 14), + (7, 3, 19), + ] + B_ = build_branching(edges) + assert_equal_branchings(B, B_) + + +def test_edmonds1_maxbranch(): + G = G1() + x = branchings.maximum_branching(G) + x_ = build_branching(optimal_arborescence_1) + assert_equal_branchings(x, x_) + + +def test_edmonds1_maxarbor(): + G = G1() + x = branchings.maximum_spanning_arborescence(G) + x_ = build_branching(optimal_arborescence_1) + assert_equal_branchings(x, x_) + + +def test_edmonds1_minimal_branching(): + # graph will have something like a minimum arborescence but no spanning one + G = nx.from_numpy_array(G_big_array, create_using=nx.DiGraph) + B = branchings.minimal_branching(G) + edges = [ + (3, 0, 5), + (0, 2, 12), + (0, 4, 12), + (2, 5, 12), + (4, 7, 12), + (5, 8, 12), + (5, 6, 14), + (2, 1, 17), + ] + B_ = build_branching(edges, double=True) + assert_equal_branchings(B, B_) + + +def test_edmonds2_maxbranch(): + G = G2() + x = branchings.maximum_branching(G) + x_ = build_branching(optimal_branching_2a) + assert_equal_branchings(x, x_) + + +def test_edmonds2_maxarbor(): + G = G2() + x = branchings.maximum_spanning_arborescence(G) + x_ = build_branching(optimal_arborescence_2) + assert_equal_branchings(x, x_) + + +def test_edmonds2_minarbor(): + G = G1() + x = branchings.minimum_spanning_arborescence(G) + # This was obtained from algorithm. Need to verify it independently. + # Branch weight is: 96 + edges = [ + (3, 0, 5), + (0, 2, 12), + (0, 4, 12), + (2, 5, 12), + (4, 7, 12), + (5, 8, 12), + (5, 6, 14), + (2, 1, 17), + ] + x_ = build_branching(edges) + assert_equal_branchings(x, x_) + + +def test_edmonds3_minbranch1(): + G = G1() + x = branchings.minimum_branching(G) + edges = [] + x_ = build_branching(edges) + assert_equal_branchings(x, x_) + + +def test_edmonds3_minbranch2(): + G = G1() + G.add_edge(8, 9, weight=-10) + x = branchings.minimum_branching(G) + edges = [(8, 9, -10)] + x_ = build_branching(edges) + assert_equal_branchings(x, x_) + + +# Need more tests + + +def test_mst(): + # Make sure we get the same results for undirected graphs. + # Example from: https://en.wikipedia.org/wiki/Kruskal's_algorithm + G = nx.Graph() + edgelist = [ + (0, 3, [("weight", 5)]), + (0, 1, [("weight", 7)]), + (1, 3, [("weight", 9)]), + (1, 2, [("weight", 8)]), + (1, 4, [("weight", 7)]), + (3, 4, [("weight", 15)]), + (3, 5, [("weight", 6)]), + (2, 4, [("weight", 5)]), + (4, 5, [("weight", 8)]), + (4, 6, [("weight", 9)]), + (5, 6, [("weight", 11)]), + ] + G.add_edges_from(edgelist) + G = G.to_directed() + x = branchings.minimum_spanning_arborescence(G) + + edges = [ + ({0, 1}, 7), + ({0, 3}, 5), + ({3, 5}, 6), + ({1, 4}, 7), + ({4, 2}, 5), + ({4, 6}, 9), + ] + + assert x.number_of_edges() == len(edges) + for u, v, d in x.edges(data=True): + assert ({u, v}, d["weight"]) in edges + + +def test_mixed_nodetypes(): + # Smoke test to make sure no TypeError is raised for mixed node types. + G = nx.Graph() + edgelist = [(0, 3, [("weight", 5)]), (0, "1", [("weight", 5)])] + G.add_edges_from(edgelist) + G = G.to_directed() + x = branchings.minimum_spanning_arborescence(G) + + +def test_edmonds1_minbranch(): + # Using -G_array and min should give the same as optimal_arborescence_1, + # but with all edges negative. + edges = [(u, v, -w) for (u, v, w) in optimal_arborescence_1] + + G = nx.from_numpy_array(-G_array, create_using=nx.DiGraph) + + # Quickly make sure max branching is empty. + x = branchings.maximum_branching(G) + x_ = build_branching([]) + assert_equal_branchings(x, x_) + + # Now test the min branching. + x = branchings.minimum_branching(G) + x_ = build_branching(edges) + assert_equal_branchings(x, x_) + + +def test_edge_attribute_preservation_normal_graph(): + # Test that edge attributes are preserved when finding an optimum graph + # using the Edmonds class for normal graphs. + G = nx.Graph() + + edgelist = [ + (0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]), + (0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]), + (1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]), + ] + G.add_edges_from(edgelist) + + B = branchings.maximum_branching(G, preserve_attrs=True) + + assert B[0][1]["otherattr"] == 1 + assert B[0][1]["otherattr2"] == 3 + + +def test_edge_attribute_preservation_multigraph(): + # Test that edge attributes are preserved when finding an optimum graph + # using the Edmonds class for multigraphs. + G = nx.MultiGraph() + + edgelist = [ + (0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]), + (0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]), + (1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]), + ] + G.add_edges_from(edgelist * 2) # Make sure we have duplicate edge paths + + B = branchings.maximum_branching(G, preserve_attrs=True) + + assert B[0][1][0]["otherattr"] == 1 + assert B[0][1][0]["otherattr2"] == 3 + + +def test_edge_attribute_discard(): + # Test that edge attributes are discarded if we do not specify to keep them + G = nx.Graph() + + edgelist = [ + (0, 1, [("weight", 5), ("otherattr", 1), ("otherattr2", 3)]), + (0, 2, [("weight", 5), ("otherattr", 2), ("otherattr2", 2)]), + (1, 2, [("weight", 6), ("otherattr", 3), ("otherattr2", 1)]), + ] + G.add_edges_from(edgelist) + + B = branchings.maximum_branching(G, preserve_attrs=False) + + edge_dict = B[0][1] + with pytest.raises(KeyError): + _ = edge_dict["otherattr"] + + +def test_partition_spanning_arborescence(): + """ + Test that we can generate minimum spanning arborescences which respect the + given partition. + """ + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + G[3][0]["partition"] = nx.EdgePartition.EXCLUDED + G[2][3]["partition"] = nx.EdgePartition.INCLUDED + G[7][3]["partition"] = nx.EdgePartition.EXCLUDED + G[0][2]["partition"] = nx.EdgePartition.EXCLUDED + G[6][2]["partition"] = nx.EdgePartition.INCLUDED + + actual_edges = [ + (0, 4, 12), + (1, 0, 4), + (1, 5, 13), + (2, 3, 21), + (4, 7, 12), + (5, 6, 14), + (5, 8, 12), + (6, 2, 21), + ] + + B = branchings.minimum_spanning_arborescence(G, partition="partition") + assert_equal_branchings(build_branching(actual_edges), B) + + +def test_arborescence_iterator_min(): + """ + Tests the arborescence iterator. + + A brute force method found 680 arborescences in this graph. + This test will not verify all of them individually, but will check two + things + + * The iterator returns 680 arborescences + * The weight of the arborescences is non-strictly increasing + + for more information please visit + https://mjschwenne.github.io/2021/06/10/implementing-the-iterators.html + """ + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + + arborescence_count = 0 + arborescence_weight = -math.inf + for B in branchings.ArborescenceIterator(G): + arborescence_count += 1 + new_arborescence_weight = B.size(weight="weight") + assert new_arborescence_weight >= arborescence_weight + arborescence_weight = new_arborescence_weight + + assert arborescence_count == 680 + + +def test_arborescence_iterator_max(): + """ + Tests the arborescence iterator. + + A brute force method found 680 arborescences in this graph. + This test will not verify all of them individually, but will check two + things + + * The iterator returns 680 arborescences + * The weight of the arborescences is non-strictly decreasing + + for more information please visit + https://mjschwenne.github.io/2021/06/10/implementing-the-iterators.html + """ + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + + arborescence_count = 0 + arborescence_weight = math.inf + for B in branchings.ArborescenceIterator(G, minimum=False): + arborescence_count += 1 + new_arborescence_weight = B.size(weight="weight") + assert new_arborescence_weight <= arborescence_weight + arborescence_weight = new_arborescence_weight + + assert arborescence_count == 680 + + +def test_arborescence_iterator_initial_partition(): + """ + Tests the arborescence iterator with three included edges and three excluded + in the initial partition. + + A brute force method similar to the one used in the above tests found that + there are 16 arborescences which contain the included edges and not the + excluded edges. + """ + G = nx.from_numpy_array(G_array, create_using=nx.DiGraph) + included_edges = [(1, 0), (5, 6), (8, 7)] + excluded_edges = [(0, 2), (3, 6), (1, 5)] + + arborescence_count = 0 + arborescence_weight = -math.inf + for B in branchings.ArborescenceIterator( + G, init_partition=(included_edges, excluded_edges) + ): + arborescence_count += 1 + new_arborescence_weight = B.size(weight="weight") + assert new_arborescence_weight >= arborescence_weight + arborescence_weight = new_arborescence_weight + for e in included_edges: + assert e in B.edges + for e in excluded_edges: + assert e not in B.edges + assert arborescence_count == 16 + + +def test_branchings_with_default_weights(): + """ + Tests that various branching algorithms work on graphs without weights. + For more information, see issue #7279. + """ + graph = nx.erdos_renyi_graph(10, p=0.2, directed=True, seed=123) + + assert all("weight" not in d for (u, v, d) in graph.edges(data=True)), ( + "test is for graphs without a weight attribute" + ) + + # Calling these functions will modify graph inplace to add weights + # copy the graph to avoid this. + nx.minimum_spanning_arborescence(graph.copy()) + nx.maximum_spanning_arborescence(graph.copy()) + nx.minimum_branching(graph.copy()) + nx.maximum_branching(graph.copy()) + nx.algorithms.tree.minimal_branching(graph.copy()) + nx.algorithms.tree.branching_weight(graph.copy()) + nx.algorithms.tree.greedy_branching(graph.copy()) + + assert all("weight" not in d for (u, v, d) in graph.edges(data=True)), ( + "The above calls should not modify the initial graph in-place" + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_coding.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_coding.py new file mode 100644 index 0000000000000000000000000000000000000000..26bd4083f52a0cc90b94c6de6d47b2c44e70a079 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_coding.py @@ -0,0 +1,114 @@ +"""Unit tests for the :mod:`~networkx.algorithms.tree.coding` module.""" + +from itertools import product + +import pytest + +import networkx as nx +from networkx.utils import edges_equal, nodes_equal + + +class TestPruferSequence: + """Unit tests for the Prüfer sequence encoding and decoding + functions. + + """ + + def test_nontree(self): + with pytest.raises(nx.NotATree): + G = nx.cycle_graph(3) + nx.to_prufer_sequence(G) + + def test_null_graph(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.to_prufer_sequence(nx.null_graph()) + + def test_trivial_graph(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.to_prufer_sequence(nx.trivial_graph()) + + def test_bad_integer_labels(self): + with pytest.raises(KeyError): + T = nx.Graph(nx.utils.pairwise("abc")) + nx.to_prufer_sequence(T) + + def test_encoding(self): + """Tests for encoding a tree as a Prüfer sequence using the + iterative strategy. + + """ + # Example from Wikipedia. + tree = nx.Graph([(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)]) + sequence = nx.to_prufer_sequence(tree) + assert sequence == [3, 3, 3, 4] + + def test_decoding(self): + """Tests for decoding a tree from a Prüfer sequence.""" + # Example from Wikipedia. + sequence = [3, 3, 3, 4] + tree = nx.from_prufer_sequence(sequence) + assert nodes_equal(list(tree), list(range(6))) + edges = [(0, 3), (1, 3), (2, 3), (3, 4), (4, 5)] + assert edges_equal(list(tree.edges()), edges) + + def test_decoding2(self): + # Example from "An Optimal Algorithm for Prufer Codes". + sequence = [2, 4, 0, 1, 3, 3] + tree = nx.from_prufer_sequence(sequence) + assert nodes_equal(list(tree), list(range(8))) + edges = [(0, 1), (0, 4), (1, 3), (2, 4), (2, 5), (3, 6), (3, 7)] + assert edges_equal(list(tree.edges()), edges) + + def test_inverse(self): + """Tests that the encoding and decoding functions are inverses.""" + for T in nx.nonisomorphic_trees(4): + T2 = nx.from_prufer_sequence(nx.to_prufer_sequence(T)) + assert nodes_equal(list(T), list(T2)) + assert edges_equal(list(T.edges()), list(T2.edges())) + + for seq in product(range(4), repeat=2): + seq2 = nx.to_prufer_sequence(nx.from_prufer_sequence(seq)) + assert list(seq) == seq2 + + +class TestNestedTuple: + """Unit tests for the nested tuple encoding and decoding functions.""" + + def test_nontree(self): + with pytest.raises(nx.NotATree): + G = nx.cycle_graph(3) + nx.to_nested_tuple(G, 0) + + def test_unknown_root(self): + with pytest.raises(nx.NodeNotFound): + G = nx.path_graph(2) + nx.to_nested_tuple(G, "bogus") + + def test_encoding(self): + T = nx.full_rary_tree(2, 2**3 - 1) + expected = (((), ()), ((), ())) + actual = nx.to_nested_tuple(T, 0) + assert nodes_equal(expected, actual) + + def test_canonical_form(self): + T = nx.Graph() + T.add_edges_from([(0, 1), (0, 2), (0, 3)]) + T.add_edges_from([(1, 4), (1, 5)]) + T.add_edges_from([(3, 6), (3, 7)]) + root = 0 + actual = nx.to_nested_tuple(T, root, canonical_form=True) + expected = ((), ((), ()), ((), ())) + assert actual == expected + + def test_decoding(self): + balanced = (((), ()), ((), ())) + expected = nx.full_rary_tree(2, 2**3 - 1) + actual = nx.from_nested_tuple(balanced) + assert nx.is_isomorphic(expected, actual) + + def test_sensible_relabeling(self): + balanced = (((), ()), ((), ())) + T = nx.from_nested_tuple(balanced, sensible_relabeling=True) + edges = [(0, 1), (0, 2), (1, 3), (1, 4), (2, 5), (2, 6)] + assert nodes_equal(list(T), list(range(2**3 - 1))) + assert edges_equal(list(T.edges()), edges) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_decomposition.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_decomposition.py new file mode 100644 index 0000000000000000000000000000000000000000..8c376053794537611f46c038ed074eb92b1ba676 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_decomposition.py @@ -0,0 +1,79 @@ +import networkx as nx +from networkx.algorithms.tree.decomposition import junction_tree + + +def test_junction_tree_directed_confounders(): + B = nx.DiGraph() + B.add_edges_from([("A", "C"), ("B", "C"), ("C", "D"), ("C", "E")]) + + G = junction_tree(B) + J = nx.Graph() + J.add_edges_from( + [ + (("C", "E"), ("C",)), + (("C",), ("A", "B", "C")), + (("A", "B", "C"), ("C",)), + (("C",), ("C", "D")), + ] + ) + + assert nx.is_isomorphic(G, J) + + +def test_junction_tree_directed_unconnected_nodes(): + B = nx.DiGraph() + B.add_nodes_from([("A", "B", "C", "D")]) + G = junction_tree(B) + + J = nx.Graph() + J.add_nodes_from([("A", "B", "C", "D")]) + + assert nx.is_isomorphic(G, J) + + +def test_junction_tree_directed_cascade(): + B = nx.DiGraph() + B.add_edges_from([("A", "B"), ("B", "C"), ("C", "D")]) + G = junction_tree(B) + + J = nx.Graph() + J.add_edges_from( + [ + (("A", "B"), ("B",)), + (("B",), ("B", "C")), + (("B", "C"), ("C",)), + (("C",), ("C", "D")), + ] + ) + assert nx.is_isomorphic(G, J) + + +def test_junction_tree_directed_unconnected_edges(): + B = nx.DiGraph() + B.add_edges_from([("A", "B"), ("C", "D"), ("E", "F")]) + G = junction_tree(B) + + J = nx.Graph() + J.add_nodes_from([("A", "B"), ("C", "D"), ("E", "F")]) + + assert nx.is_isomorphic(G, J) + + +def test_junction_tree_undirected(): + B = nx.Graph() + B.add_edges_from([("A", "C"), ("A", "D"), ("B", "C"), ("C", "E")]) + G = junction_tree(B) + + J = nx.Graph() + J.add_edges_from( + [ + (("A", "D"), ("A",)), + (("A",), ("A", "C")), + (("A", "C"), ("C",)), + (("C",), ("B", "C")), + (("B", "C"), ("C",)), + (("C",), ("C", "E")), + ] + ) + + assert nx.is_isomorphic(G, J) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_distance_measures.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_distance_measures.py new file mode 100644 index 0000000000000000000000000000000000000000..c595d35954c22965fd2aa5272c62dd22af65018f --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_distance_measures.py @@ -0,0 +1,99 @@ +import math + +import pytest + +import networkx as nx + + +class TestCenter: + @pytest.mark.parametrize("graph_type", (nx.Graph, nx.MultiGraph)) + def test_center_simple_tree(self, graph_type): + G = nx.Graph([(1, 2), (1, 3), (2, 4), (2, 5)], create_using=graph_type) + assert set(nx.tree.center(G)) == {1, 2} + + @pytest.mark.parametrize("r", range(2, 5)) + @pytest.mark.parametrize("h", range(1, 5)) + def test_center_balanced_tree(self, r, h): + G = nx.balanced_tree(r, h) + assert nx.tree.center(G) == [0] + + @pytest.mark.parametrize("n", [1, 2, 99, 100]) + def test_center_path_graph(self, n): + G = nx.path_graph(n) + expected = {(n - 1) // 2, math.ceil((n - 1) / 2)} + assert set(nx.tree.center(G)) == expected + + @pytest.mark.parametrize("n", [0, 2, 3, 5, 99, 100]) + def test_center_star_graph(self, n): + G = nx.star_graph(n) + assert nx.tree.center(G) == [0] + + @pytest.mark.parametrize( + "G", + ( + nx.cycle_graph(5), + nx.complete_graph(5), + nx.Graph([(0, 1), (2, 3)]), + nx.empty_graph(2), + nx.Graph(), + nx.MultiGraph([(0, 1), (0, 1)]), + nx.Graph([(0, 1), (1, 2), (3, 4)]), + nx.Graph([(0, 1), (1, 2), (3, 4), (4, 5)]), + pytest.param( + nx.Graph([(0, 0)]), + marks=pytest.mark.xfail(reason="no check for self-loops"), + ), + ), + ) + def test_center_non_tree(self, G): + with pytest.raises(nx.NotATree, match=r"not a tree"): + nx.tree.center(G) + + @pytest.mark.parametrize("graph_type", (nx.DiGraph, nx.MultiDiGraph)) + def test_center_directed(self, graph_type): + G = nx.path_graph(4, create_using=graph_type) + with pytest.raises( + nx.NetworkXNotImplemented, match=r"not implemented for directed" + ): + nx.tree.center(G) + + +class TestDistance: + @pytest.mark.parametrize("n", [1, 2, 99, 100]) + def test_tree_centroid_path_graphs(self, n): + G = nx.path_graph(n) + expected = {(n - 1) // 2, math.ceil((n - 1) / 2)} + assert set(nx.tree.centroid(G)) == expected + + @pytest.mark.parametrize("r", range(2, 5)) + @pytest.mark.parametrize("h", range(1, 5)) + def test_tree_centroid_balanced_tree(self, r, h): + G = nx.balanced_tree(r, h) + assert nx.tree.centroid(G) == [0] + + def test_tree_centroid_multiple_centroids(self): + G = nx.full_rary_tree(2, 8) + assert nx.tree.centroid(G) == [0, 1] + + def test_tree_centroid_different_from_graph_center(self): + G = nx.star_graph(6) + nx.add_path(G, [6, 7, 8, 9, 10]) + # nx.center(G) would be [7] + assert nx.tree.centroid(G) == [0] + + def test_tree_centroid_not_a_tree(self): + G = nx.cycle_graph(3) + with pytest.raises(nx.NotATree, match=r"not a tree"): + nx.tree.centroid(G) + + @pytest.mark.parametrize("G", [nx.DiGraph([(0, 1)]), nx.MultiDiGraph([(0, 1)])]) + def test_tree_centroid_direct_raises(self, G): + with pytest.raises( + nx.NetworkXNotImplemented, match=r"not implemented for directed type" + ): + nx.tree.centroid(G) + + def test_tree_centroid_empty(self): + G = nx.Graph() + with pytest.raises(nx.NetworkXPointlessConcept, match=r"has no nodes"): + nx.tree.centroid(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_mst.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_mst.py new file mode 100644 index 0000000000000000000000000000000000000000..66ed8652e332fab22813c73b0ea419fd1ca6136a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_mst.py @@ -0,0 +1,934 @@ +"""Unit tests for the :mod:`networkx.algorithms.tree.mst` module.""" + +import pytest + +import networkx as nx +from networkx.utils import edges_equal, nodes_equal + + +def test_unknown_algorithm(): + with pytest.raises(ValueError): + nx.minimum_spanning_tree(nx.Graph(), algorithm="random") + with pytest.raises( + ValueError, match="random is not a valid choice for an algorithm." + ): + nx.maximum_spanning_edges(nx.Graph(), algorithm="random") + + +class MinimumSpanningTreeTestBase: + """Base class for test classes for minimum spanning tree algorithms. + This class contains some common tests that will be inherited by + subclasses. Each subclass must have a class attribute + :data:`algorithm` that is a string representing the algorithm to + run, as described under the ``algorithm`` keyword argument for the + :func:`networkx.minimum_spanning_edges` function. Subclasses can + then implement any algorithm-specific tests. + """ + + def setup_method(self, method): + """Creates an example graph and stores the expected minimum and + maximum spanning tree edges. + """ + # This stores the class attribute `algorithm` in an instance attribute. + self.algo = self.algorithm + # This example graph comes from Wikipedia: + # https://en.wikipedia.org/wiki/Kruskal's_algorithm + edges = [ + (0, 1, 7), + (0, 3, 5), + (1, 2, 8), + (1, 3, 9), + (1, 4, 7), + (2, 4, 5), + (3, 4, 15), + (3, 5, 6), + (4, 5, 8), + (4, 6, 9), + (5, 6, 11), + ] + self.G = nx.Graph() + self.G.add_weighted_edges_from(edges) + self.minimum_spanning_edgelist = [ + (0, 1, {"weight": 7}), + (0, 3, {"weight": 5}), + (1, 4, {"weight": 7}), + (2, 4, {"weight": 5}), + (3, 5, {"weight": 6}), + (4, 6, {"weight": 9}), + ] + self.maximum_spanning_edgelist = [ + (0, 1, {"weight": 7}), + (1, 2, {"weight": 8}), + (1, 3, {"weight": 9}), + (3, 4, {"weight": 15}), + (4, 6, {"weight": 9}), + (5, 6, {"weight": 11}), + ] + + def test_minimum_edges(self): + edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo) + # Edges from the spanning edges functions don't come in sorted + # orientation, so we need to sort each edge individually. + actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges) + assert edges_equal(actual, self.minimum_spanning_edgelist) + + def test_maximum_edges(self): + edges = nx.maximum_spanning_edges(self.G, algorithm=self.algo) + # Edges from the spanning edges functions don't come in sorted + # orientation, so we need to sort each edge individually. + actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges) + assert edges_equal(actual, self.maximum_spanning_edgelist) + + def test_without_data(self): + edges = nx.minimum_spanning_edges(self.G, algorithm=self.algo, data=False) + # Edges from the spanning edges functions don't come in sorted + # orientation, so we need to sort each edge individually. + actual = sorted((min(u, v), max(u, v)) for u, v in edges) + expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist] + assert edges_equal(actual, expected) + + def test_nan_weights(self): + # Edge weights NaN never appear in the spanning tree. see #2164 + G = self.G + G.add_edge(0, 12, weight=float("nan")) + edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, data=False, ignore_nan=True + ) + actual = sorted((min(u, v), max(u, v)) for u, v in edges) + expected = [(u, v) for u, v, d in self.minimum_spanning_edgelist] + assert edges_equal(actual, expected) + # Now test for raising exception + edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, data=False, ignore_nan=False + ) + with pytest.raises(ValueError): + list(edges) + # test default for ignore_nan as False + edges = nx.minimum_spanning_edges(G, algorithm=self.algo, data=False) + with pytest.raises(ValueError): + list(edges) + + def test_nan_weights_MultiGraph(self): + G = nx.MultiGraph() + G.add_edge(0, 12, weight=float("nan")) + edges = nx.minimum_spanning_edges( + G, algorithm="prim", data=False, ignore_nan=False + ) + with pytest.raises(ValueError): + list(edges) + # test default for ignore_nan as False + edges = nx.minimum_spanning_edges(G, algorithm="prim", data=False) + with pytest.raises(ValueError): + list(edges) + + def test_nan_weights_order(self): + # now try again with a nan edge at the beginning of G.nodes + edges = [ + (0, 1, 7), + (0, 3, 5), + (1, 2, 8), + (1, 3, 9), + (1, 4, 7), + (2, 4, 5), + (3, 4, 15), + (3, 5, 6), + (4, 5, 8), + (4, 6, 9), + (5, 6, 11), + ] + G = nx.Graph() + G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges]) + G.add_edge(0, 7, weight=float("nan")) + edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, data=False, ignore_nan=True + ) + actual = sorted((min(u, v), max(u, v)) for u, v in edges) + shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist] + assert edges_equal(actual, shift) + + def test_isolated_node(self): + # now try again with an isolated node + edges = [ + (0, 1, 7), + (0, 3, 5), + (1, 2, 8), + (1, 3, 9), + (1, 4, 7), + (2, 4, 5), + (3, 4, 15), + (3, 5, 6), + (4, 5, 8), + (4, 6, 9), + (5, 6, 11), + ] + G = nx.Graph() + G.add_weighted_edges_from([(u + 1, v + 1, wt) for u, v, wt in edges]) + G.add_node(0) + edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, data=False, ignore_nan=True + ) + actual = sorted((min(u, v), max(u, v)) for u, v in edges) + shift = [(u + 1, v + 1) for u, v, d in self.minimum_spanning_edgelist] + assert edges_equal(actual, shift) + + def test_minimum_tree(self): + T = nx.minimum_spanning_tree(self.G, algorithm=self.algo) + actual = sorted(T.edges(data=True)) + assert edges_equal(actual, self.minimum_spanning_edgelist) + + def test_maximum_tree(self): + T = nx.maximum_spanning_tree(self.G, algorithm=self.algo) + actual = sorted(T.edges(data=True)) + assert edges_equal(actual, self.maximum_spanning_edgelist) + + def test_disconnected(self): + G = nx.Graph([(0, 1, {"weight": 1}), (2, 3, {"weight": 2})]) + T = nx.minimum_spanning_tree(G, algorithm=self.algo) + assert nodes_equal(list(T), list(range(4))) + assert edges_equal(list(T.edges()), [(0, 1), (2, 3)]) + + def test_empty_graph(self): + G = nx.empty_graph(3) + T = nx.minimum_spanning_tree(G, algorithm=self.algo) + assert nodes_equal(sorted(T), list(range(3))) + assert T.number_of_edges() == 0 + + def test_attributes(self): + G = nx.Graph() + G.add_edge(1, 2, weight=1, color="red", distance=7) + G.add_edge(2, 3, weight=1, color="green", distance=2) + G.add_edge(1, 3, weight=10, color="blue", distance=1) + G.graph["foo"] = "bar" + T = nx.minimum_spanning_tree(G, algorithm=self.algo) + assert T.graph == G.graph + assert nodes_equal(T, G) + for u, v in T.edges(): + assert T.adj[u][v] == G.adj[u][v] + + def test_weight_attribute(self): + G = nx.Graph() + G.add_edge(0, 1, weight=1, distance=7) + G.add_edge(0, 2, weight=30, distance=1) + G.add_edge(1, 2, weight=1, distance=1) + G.add_node(3) + T = nx.minimum_spanning_tree(G, algorithm=self.algo, weight="distance") + assert nodes_equal(sorted(T), list(range(4))) + assert edges_equal(sorted(T.edges()), [(0, 2), (1, 2)]) + T = nx.maximum_spanning_tree(G, algorithm=self.algo, weight="distance") + assert nodes_equal(sorted(T), list(range(4))) + assert edges_equal(sorted(T.edges()), [(0, 1), (0, 2)]) + + def test_minimum_spanning_edges_directed_raises(self): + DG = nx.DiGraph() + DG.add_edge(0, 1, weight=1) + with pytest.raises(nx.NetworkXNotImplemented): + list(nx.minimum_spanning_edges(DG, algorithm=self.algo)) + with pytest.raises(nx.NetworkXNotImplemented): + list(nx.maximum_spanning_edges(DG, algorithm=self.algo)) + + +class TestBoruvka(MinimumSpanningTreeTestBase): + """Unit tests for computing a minimum (or maximum) spanning tree + using Borůvka's algorithm. + """ + + algorithm = "boruvka" + + def test_unicode_name(self): + """Tests that using a Unicode string can correctly indicate + Borůvka's algorithm. + """ + edges = nx.minimum_spanning_edges(self.G, algorithm="borůvka") + # Edges from the spanning edges functions don't come in sorted + # orientation, so we need to sort each edge individually. + actual = sorted((min(u, v), max(u, v), d) for u, v, d in edges) + assert edges_equal(actual, self.minimum_spanning_edgelist) + + def test_minimum_spanning_edges_multigraph_raises(self): + MG = nx.MultiGraph() + MG.add_edge(0, 1, weight=1) + with pytest.raises(nx.NetworkXNotImplemented): + list(nx.minimum_spanning_edges(MG, algorithm=self.algo)) + with pytest.raises(nx.NetworkXNotImplemented): + list(nx.maximum_spanning_edges(MG, algorithm=self.algo)) + + +class MultigraphMSTTestBase(MinimumSpanningTreeTestBase): + # Abstract class + + def test_multigraph_keys_min(self): + """Tests that the minimum spanning edges of a multigraph + preserves edge keys. + """ + G = nx.MultiGraph() + G.add_edge(0, 1, key="a", weight=2) + G.add_edge(0, 1, key="b", weight=1) + min_edges = nx.minimum_spanning_edges + mst_edges = min_edges(G, algorithm=self.algo, data=False) + assert edges_equal([(0, 1, "b")], list(mst_edges)) + + def test_multigraph_keys_max(self): + """Tests that the maximum spanning edges of a multigraph + preserves edge keys. + """ + G = nx.MultiGraph() + G.add_edge(0, 1, key="a", weight=2) + G.add_edge(0, 1, key="b", weight=1) + max_edges = nx.maximum_spanning_edges + mst_edges = max_edges(G, algorithm=self.algo, data=False) + assert edges_equal([(0, 1, "a")], list(mst_edges)) + + +class TestKruskal(MultigraphMSTTestBase): + """Unit tests for computing a minimum (or maximum) spanning tree + using Kruskal's algorithm. + """ + + algorithm = "kruskal" + + def test_key_data_bool(self): + """Tests that the keys and data values are included in + MST edges based on whether keys and data parameters are + true or false""" + G = nx.MultiGraph() + G.add_edge(1, 2, key=1, weight=2) + G.add_edge(1, 2, key=2, weight=3) + G.add_edge(3, 2, key=1, weight=2) + G.add_edge(3, 1, key=1, weight=4) + + # keys are included and data is not included + mst_edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, keys=True, data=False + ) + assert edges_equal([(1, 2, 1), (2, 3, 1)], list(mst_edges)) + + # keys are not included and data is included + mst_edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, keys=False, data=True + ) + assert edges_equal( + [(1, 2, {"weight": 2}), (2, 3, {"weight": 2})], list(mst_edges) + ) + + # both keys and data are not included + mst_edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, keys=False, data=False + ) + assert edges_equal([(1, 2), (2, 3)], list(mst_edges)) + + # both keys and data are included + mst_edges = nx.minimum_spanning_edges( + G, algorithm=self.algo, keys=True, data=True + ) + assert edges_equal( + [(1, 2, 1, {"weight": 2}), (2, 3, 1, {"weight": 2})], list(mst_edges) + ) + + +class TestPrim(MultigraphMSTTestBase): + """Unit tests for computing a minimum (or maximum) spanning tree + using Prim's algorithm. + """ + + algorithm = "prim" + + def test_prim_mst_edges_simple_graph(self): + H = nx.Graph() + H.add_edge(1, 2, key=2, weight=3) + H.add_edge(3, 2, key=1, weight=2) + H.add_edge(3, 1, key=1, weight=4) + + mst_edges = nx.minimum_spanning_edges(H, algorithm=self.algo, ignore_nan=True) + assert edges_equal( + [(1, 2, {"key": 2, "weight": 3}), (2, 3, {"key": 1, "weight": 2})], + list(mst_edges), + ) + + def test_ignore_nan(self): + """Tests that the edges with NaN weights are ignored or + raise an Error based on ignore_nan is true or false""" + H = nx.MultiGraph() + H.add_edge(1, 2, key=1, weight=float("nan")) + H.add_edge(1, 2, key=2, weight=3) + H.add_edge(3, 2, key=1, weight=2) + H.add_edge(3, 1, key=1, weight=4) + + # NaN weight edges are ignored when ignore_nan=True + mst_edges = nx.minimum_spanning_edges(H, algorithm=self.algo, ignore_nan=True) + assert edges_equal( + [(1, 2, 2, {"weight": 3}), (2, 3, 1, {"weight": 2})], list(mst_edges) + ) + + # NaN weight edges raise Error when ignore_nan=False + with pytest.raises(ValueError): + list(nx.minimum_spanning_edges(H, algorithm=self.algo, ignore_nan=False)) + + def test_multigraph_keys_tree(self): + G = nx.MultiGraph() + G.add_edge(0, 1, key="a", weight=2) + G.add_edge(0, 1, key="b", weight=1) + T = nx.minimum_spanning_tree(G, algorithm=self.algo) + assert edges_equal([(0, 1, 1)], list(T.edges(data="weight"))) + + def test_multigraph_keys_tree_max(self): + G = nx.MultiGraph() + G.add_edge(0, 1, key="a", weight=2) + G.add_edge(0, 1, key="b", weight=1) + T = nx.maximum_spanning_tree(G, algorithm=self.algo) + assert edges_equal([(0, 1, 2)], list(T.edges(data="weight"))) + + +class TestSpanningTreeIterator: + """ + Tests the spanning tree iterator on the example graph in the 2005 Sörensen + and Janssens paper An Algorithm to Generate all Spanning Trees of a Graph in + Order of Increasing Cost + """ + + def setup_method(self): + # Original Graph + edges = [(0, 1, 5), (1, 2, 4), (1, 4, 6), (2, 3, 5), (2, 4, 7), (3, 4, 3)] + self.G = nx.Graph() + self.G.add_weighted_edges_from(edges) + # List of lists of spanning trees in increasing order + self.spanning_trees = [ + # 1, MST, cost = 17 + [ + (0, 1, {"weight": 5}), + (1, 2, {"weight": 4}), + (2, 3, {"weight": 5}), + (3, 4, {"weight": 3}), + ], + # 2, cost = 18 + [ + (0, 1, {"weight": 5}), + (1, 2, {"weight": 4}), + (1, 4, {"weight": 6}), + (3, 4, {"weight": 3}), + ], + # 3, cost = 19 + [ + (0, 1, {"weight": 5}), + (1, 4, {"weight": 6}), + (2, 3, {"weight": 5}), + (3, 4, {"weight": 3}), + ], + # 4, cost = 19 + [ + (0, 1, {"weight": 5}), + (1, 2, {"weight": 4}), + (2, 4, {"weight": 7}), + (3, 4, {"weight": 3}), + ], + # 5, cost = 20 + [ + (0, 1, {"weight": 5}), + (1, 2, {"weight": 4}), + (1, 4, {"weight": 6}), + (2, 3, {"weight": 5}), + ], + # 6, cost = 21 + [ + (0, 1, {"weight": 5}), + (1, 4, {"weight": 6}), + (2, 4, {"weight": 7}), + (3, 4, {"weight": 3}), + ], + # 7, cost = 21 + [ + (0, 1, {"weight": 5}), + (1, 2, {"weight": 4}), + (2, 3, {"weight": 5}), + (2, 4, {"weight": 7}), + ], + # 8, cost = 23 + [ + (0, 1, {"weight": 5}), + (1, 4, {"weight": 6}), + (2, 3, {"weight": 5}), + (2, 4, {"weight": 7}), + ], + ] + + def test_minimum_spanning_tree_iterator(self): + """ + Tests that the spanning trees are correctly returned in increasing order + """ + tree_index = 0 + for tree in nx.SpanningTreeIterator(self.G): + actual = sorted(tree.edges(data=True)) + assert edges_equal(actual, self.spanning_trees[tree_index]) + tree_index += 1 + + def test_maximum_spanning_tree_iterator(self): + """ + Tests that the spanning trees are correctly returned in decreasing order + """ + tree_index = 7 + for tree in nx.SpanningTreeIterator(self.G, minimum=False): + actual = sorted(tree.edges(data=True)) + assert edges_equal(actual, self.spanning_trees[tree_index]) + tree_index -= 1 + + +class TestSpanningTreeMultiGraphIterator: + """ + Uses the same graph as the above class but with an added edge of twice the weight. + """ + + def setup_method(self): + # New graph + edges = [ + (0, 1, 5), + (0, 1, 10), + (1, 2, 4), + (1, 2, 8), + (1, 4, 6), + (1, 4, 12), + (2, 3, 5), + (2, 3, 10), + (2, 4, 7), + (2, 4, 14), + (3, 4, 3), + (3, 4, 6), + ] + self.G = nx.MultiGraph() + self.G.add_weighted_edges_from(edges) + + # There are 128 trees. I'd rather not list all 128 here, and computing them + # on such a small graph actually doesn't take that long. + from itertools import combinations + + self.spanning_trees = [] + for e in combinations(self.G.edges, 4): + tree = self.G.edge_subgraph(e) + if nx.is_tree(tree): + self.spanning_trees.append(sorted(tree.edges(keys=True, data=True))) + + def test_minimum_spanning_tree_iterator_multigraph(self): + """ + Tests that the spanning trees are correctly returned in increasing order + """ + tree_index = 0 + last_weight = 0 + for tree in nx.SpanningTreeIterator(self.G): + actual = sorted(tree.edges(keys=True, data=True)) + weight = sum([e[3]["weight"] for e in actual]) + assert actual in self.spanning_trees + assert weight >= last_weight + tree_index += 1 + + def test_maximum_spanning_tree_iterator_multigraph(self): + """ + Tests that the spanning trees are correctly returned in decreasing order + """ + tree_index = 127 + # Maximum weight tree is 46 + last_weight = 50 + for tree in nx.SpanningTreeIterator(self.G, minimum=False): + actual = sorted(tree.edges(keys=True, data=True)) + weight = sum([e[3]["weight"] for e in actual]) + assert actual in self.spanning_trees + assert weight <= last_weight + tree_index -= 1 + + +def test_random_spanning_tree_multiplicative_small(): + """ + Using a fixed seed, sample one tree for repeatability. + """ + from math import exp + + pytest.importorskip("scipy") + + gamma = { + (0, 1): -0.6383, + (0, 2): -0.6827, + (0, 5): 0, + (1, 2): -1.0781, + (1, 4): 0, + (2, 3): 0, + (5, 3): -0.2820, + (5, 4): -0.3327, + (4, 3): -0.9927, + } + + # The undirected support of gamma + G = nx.Graph() + for u, v in gamma: + G.add_edge(u, v, lambda_key=exp(gamma[(u, v)])) + + solution_edges = [(2, 3), (3, 4), (0, 5), (5, 4), (4, 1)] + solution = nx.Graph() + solution.add_edges_from(solution_edges) + + sampled_tree = nx.random_spanning_tree(G, "lambda_key", seed=42) + + assert nx.utils.edges_equal(solution.edges, sampled_tree.edges) + + +@pytest.mark.slow +def test_random_spanning_tree_multiplicative_large(): + """ + Sample many trees from the distribution created in the last test + """ + from math import exp + from random import Random + + pytest.importorskip("numpy") + stats = pytest.importorskip("scipy.stats") + + gamma = { + (0, 1): -0.6383, + (0, 2): -0.6827, + (0, 5): 0, + (1, 2): -1.0781, + (1, 4): 0, + (2, 3): 0, + (5, 3): -0.2820, + (5, 4): -0.3327, + (4, 3): -0.9927, + } + + # The undirected support of gamma + G = nx.Graph() + for u, v in gamma: + G.add_edge(u, v, lambda_key=exp(gamma[(u, v)])) + + # Find the multiplicative weight for each tree. + total_weight = 0 + tree_expected = {} + for t in nx.SpanningTreeIterator(G): + # Find the multiplicative weight of the spanning tree + weight = 1 + for u, v, d in t.edges(data="lambda_key"): + weight *= d + tree_expected[t] = weight + total_weight += weight + + # Assert that every tree has an entry in the expected distribution + assert len(tree_expected) == 75 + + # Set the sample size and then calculate the expected number of times we + # expect to see each tree. This test uses a near minimum sample size where + # the most unlikely tree has an expected frequency of 5.15. + # (Minimum required is 5) + # + # Here we also initialize the tree_actual dict so that we know the keys + # match between the two. We will later take advantage of the fact that since + # python 3.7 dict order is guaranteed so the expected and actual data will + # have the same order. + sample_size = 1200 + tree_actual = {} + for t in tree_expected: + tree_expected[t] = (tree_expected[t] / total_weight) * sample_size + tree_actual[t] = 0 + + # Sample the spanning trees + # + # Assert that they are actually trees and record which of the 75 trees we + # have sampled. + # + # For repeatability, we want to take advantage of the decorators in NetworkX + # to randomly sample the same sample each time. However, if we pass in a + # constant seed to sample_spanning_tree we will get the same tree each time. + # Instead, we can create our own random number generator with a fixed seed + # and pass those into sample_spanning_tree. + rng = Random(37) + for _ in range(sample_size): + sampled_tree = nx.random_spanning_tree(G, "lambda_key", seed=rng) + assert nx.is_tree(sampled_tree) + + for t in tree_expected: + if nx.utils.edges_equal(t.edges, sampled_tree.edges): + tree_actual[t] += 1 + break + + # Conduct a Chi squared test to see if the actual distribution matches the + # expected one at an alpha = 0.05 significance level. + # + # H_0: The distribution of trees in tree_actual matches the normalized product + # of the edge weights in the tree. + # + # H_a: The distribution of trees in tree_actual follows some other + # distribution of spanning trees. + _, p = stats.chisquare(list(tree_actual.values()), list(tree_expected.values())) + + # Assert that p is greater than the significance level so that we do not + # reject the null hypothesis + assert not p < 0.05 + + +def test_random_spanning_tree_additive_small(): + """ + Sample a single spanning tree from the additive method. + """ + pytest.importorskip("scipy") + + edges = { + (0, 1): 1, + (0, 2): 1, + (0, 5): 3, + (1, 2): 2, + (1, 4): 3, + (2, 3): 3, + (5, 3): 4, + (5, 4): 5, + (4, 3): 4, + } + + # Build the graph + G = nx.Graph() + for u, v in edges: + G.add_edge(u, v, weight=edges[(u, v)]) + + solution_edges = [(0, 2), (1, 2), (2, 3), (3, 4), (3, 5)] + solution = nx.Graph() + solution.add_edges_from(solution_edges) + + sampled_tree = nx.random_spanning_tree( + G, weight="weight", multiplicative=False, seed=37 + ) + + assert nx.utils.edges_equal(solution.edges, sampled_tree.edges) + + +@pytest.mark.slow +def test_random_spanning_tree_additive_large(): + """ + Sample many spanning trees from the additive method. + """ + from random import Random + + pytest.importorskip("numpy") + stats = pytest.importorskip("scipy.stats") + + edges = { + (0, 1): 1, + (0, 2): 1, + (0, 5): 3, + (1, 2): 2, + (1, 4): 3, + (2, 3): 3, + (5, 3): 4, + (5, 4): 5, + (4, 3): 4, + } + + # Build the graph + G = nx.Graph() + for u, v in edges: + G.add_edge(u, v, weight=edges[(u, v)]) + + # Find the additive weight for each tree. + total_weight = 0 + tree_expected = {} + for t in nx.SpanningTreeIterator(G): + # Find the multiplicative weight of the spanning tree + weight = 0 + for u, v, d in t.edges(data="weight"): + weight += d + tree_expected[t] = weight + total_weight += weight + + # Assert that every tree has an entry in the expected distribution + assert len(tree_expected) == 75 + + # Set the sample size and then calculate the expected number of times we + # expect to see each tree. This test uses a near minimum sample size where + # the most unlikely tree has an expected frequency of 5.07. + # (Minimum required is 5) + # + # Here we also initialize the tree_actual dict so that we know the keys + # match between the two. We will later take advantage of the fact that since + # python 3.7 dict order is guaranteed so the expected and actual data will + # have the same order. + sample_size = 500 + tree_actual = {} + for t in tree_expected: + tree_expected[t] = (tree_expected[t] / total_weight) * sample_size + tree_actual[t] = 0 + + # Sample the spanning trees + # + # Assert that they are actually trees and record which of the 75 trees we + # have sampled. + # + # For repeatability, we want to take advantage of the decorators in NetworkX + # to randomly sample the same sample each time. However, if we pass in a + # constant seed to sample_spanning_tree we will get the same tree each time. + # Instead, we can create our own random number generator with a fixed seed + # and pass those into sample_spanning_tree. + rng = Random(37) + for _ in range(sample_size): + sampled_tree = nx.random_spanning_tree( + G, "weight", multiplicative=False, seed=rng + ) + assert nx.is_tree(sampled_tree) + + for t in tree_expected: + if nx.utils.edges_equal(t.edges, sampled_tree.edges): + tree_actual[t] += 1 + break + + # Conduct a Chi squared test to see if the actual distribution matches the + # expected one at an alpha = 0.05 significance level. + # + # H_0: The distribution of trees in tree_actual matches the normalized product + # of the edge weights in the tree. + # + # H_a: The distribution of trees in tree_actual follows some other + # distribution of spanning trees. + _, p = stats.chisquare(list(tree_actual.values()), list(tree_expected.values())) + + # Assert that p is greater than the significance level so that we do not + # reject the null hypothesis + assert not p < 0.05 + + +def test_random_spanning_tree_empty_graph(): + G = nx.Graph() + rst = nx.tree.random_spanning_tree(G) + assert len(rst.nodes) == 0 + assert len(rst.edges) == 0 + + +def test_random_spanning_tree_single_node_graph(): + G = nx.Graph() + G.add_node(0) + rst = nx.tree.random_spanning_tree(G) + assert len(rst.nodes) == 1 + assert len(rst.edges) == 0 + + +def test_random_spanning_tree_single_node_loop(): + G = nx.Graph() + G.add_node(0) + G.add_edge(0, 0) + rst = nx.tree.random_spanning_tree(G) + assert len(rst.nodes) == 1 + assert len(rst.edges) == 0 + + +class TestNumberSpanningTrees: + @classmethod + def setup_class(cls): + global np + np = pytest.importorskip("numpy") + sp = pytest.importorskip("scipy") + + def test_nst_disconnected(self): + G = nx.empty_graph(2) + assert np.isclose(nx.number_of_spanning_trees(G), 0) + + def test_nst_no_nodes(self): + G = nx.Graph() + with pytest.raises(nx.NetworkXPointlessConcept): + nx.number_of_spanning_trees(G) + + def test_nst_weight(self): + G = nx.Graph() + G.add_edge(1, 2, weight=1) + G.add_edge(1, 3, weight=1) + G.add_edge(2, 3, weight=2) + # weights are ignored + assert np.isclose(nx.number_of_spanning_trees(G), 3) + # including weight + assert np.isclose(nx.number_of_spanning_trees(G, weight="weight"), 5) + + def test_nst_negative_weight(self): + G = nx.Graph() + G.add_edge(1, 2, weight=1) + G.add_edge(1, 3, weight=-1) + G.add_edge(2, 3, weight=-2) + # weights are ignored + assert np.isclose(nx.number_of_spanning_trees(G), 3) + # including weight + assert np.isclose(nx.number_of_spanning_trees(G, weight="weight"), -1) + + def test_nst_selfloop(self): + # self-loops are ignored + G = nx.complete_graph(3) + G.add_edge(1, 1) + assert np.isclose(nx.number_of_spanning_trees(G), 3) + + def test_nst_multigraph(self): + G = nx.MultiGraph() + G.add_edge(1, 2) + G.add_edge(1, 2) + G.add_edge(1, 3) + G.add_edge(2, 3) + assert np.isclose(nx.number_of_spanning_trees(G), 5) + + def test_nst_complete_graph(self): + # this is known as Cayley's formula + N = 5 + G = nx.complete_graph(N) + assert np.isclose(nx.number_of_spanning_trees(G), N ** (N - 2)) + + def test_nst_path_graph(self): + G = nx.path_graph(5) + assert np.isclose(nx.number_of_spanning_trees(G), 1) + + def test_nst_cycle_graph(self): + G = nx.cycle_graph(5) + assert np.isclose(nx.number_of_spanning_trees(G), 5) + + def test_nst_directed_noroot(self): + G = nx.empty_graph(3, create_using=nx.MultiDiGraph) + with pytest.raises(nx.NetworkXError): + nx.number_of_spanning_trees(G) + + def test_nst_directed_root_not_exist(self): + G = nx.empty_graph(3, create_using=nx.MultiDiGraph) + with pytest.raises(nx.NetworkXError): + nx.number_of_spanning_trees(G, root=42) + + def test_nst_directed_not_weak_connected(self): + G = nx.DiGraph() + G.add_edge(1, 2) + G.add_edge(3, 4) + assert np.isclose(nx.number_of_spanning_trees(G, root=1), 0) + + def test_nst_directed_cycle_graph(self): + G = nx.DiGraph() + G = nx.cycle_graph(7, G) + assert np.isclose(nx.number_of_spanning_trees(G, root=0), 1) + + def test_nst_directed_complete_graph(self): + G = nx.DiGraph() + G = nx.complete_graph(7, G) + assert np.isclose(nx.number_of_spanning_trees(G, root=0), 7**5) + + def test_nst_directed_multi(self): + G = nx.MultiDiGraph() + G = nx.cycle_graph(3, G) + G.add_edge(1, 2) + assert np.isclose(nx.number_of_spanning_trees(G, root=0), 2) + + def test_nst_directed_selfloop(self): + G = nx.MultiDiGraph() + G = nx.cycle_graph(3, G) + G.add_edge(1, 1) + assert np.isclose(nx.number_of_spanning_trees(G, root=0), 1) + + def test_nst_directed_weak_connected(self): + G = nx.MultiDiGraph() + G = nx.cycle_graph(3, G) + G.remove_edge(1, 2) + assert np.isclose(nx.number_of_spanning_trees(G, root=0), 0) + + def test_nst_directed_weighted(self): + # from root=1: + # arborescence 1: 1->2, 1->3, weight=2*1 + # arborescence 2: 1->2, 2->3, weight=2*3 + G = nx.DiGraph() + G.add_edge(1, 2, weight=2) + G.add_edge(1, 3, weight=1) + G.add_edge(2, 3, weight=3) + Nst = nx.number_of_spanning_trees(G, root=1, weight="weight") + assert np.isclose(Nst, 8) + Nst = nx.number_of_spanning_trees(G, root=2, weight="weight") + assert np.isclose(Nst, 0) + Nst = nx.number_of_spanning_trees(G, root=3, weight="weight") + assert np.isclose(Nst, 0) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_operations.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_operations.py new file mode 100644 index 0000000000000000000000000000000000000000..284d94e2e5059de267b5ea47f6012a42c6ac4639 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_operations.py @@ -0,0 +1,53 @@ +from itertools import chain + +import networkx as nx +from networkx.utils import edges_equal, nodes_equal + + +def _check_custom_label_attribute(input_trees, res_tree, label_attribute): + res_attr_dict = nx.get_node_attributes(res_tree, label_attribute) + res_attr_set = set(res_attr_dict.values()) + input_label = (tree for tree, root in input_trees) + input_label_set = set(chain.from_iterable(input_label)) + return res_attr_set == input_label_set + + +def test_empty_sequence(): + """Joining the empty sequence results in the tree with one node.""" + T = nx.join_trees([]) + assert len(T) == 1 + assert T.number_of_edges() == 0 + + +def test_single(): + """Joining just one tree yields a tree with one more node.""" + T = nx.empty_graph(1) + trees = [(T, 0)] + actual_with_label = nx.join_trees(trees, label_attribute="custom_label") + expected = nx.path_graph(2) + assert nodes_equal(list(expected), list(actual_with_label)) + assert edges_equal(list(expected.edges()), list(actual_with_label.edges())) + + +def test_basic(): + """Joining multiple subtrees at a root node.""" + trees = [(nx.full_rary_tree(2, 2**2 - 1), 0) for i in range(2)] + expected = nx.full_rary_tree(2, 2**3 - 1) + actual = nx.join_trees(trees, label_attribute="old_labels") + assert nx.is_isomorphic(actual, expected) + assert _check_custom_label_attribute(trees, actual, "old_labels") + + actual_without_label = nx.join_trees(trees) + assert nx.is_isomorphic(actual_without_label, expected) + # check that no labels were stored + assert all(not data for _, data in actual_without_label.nodes(data=True)) + + +def test_first_label(): + """Test the functionality of the first_label argument.""" + T1 = nx.path_graph(3) + T2 = nx.path_graph(2) + actual = nx.join_trees([(T1, 0), (T2, 0)], first_label=10) + expected_nodes = set(range(10, 16)) + assert set(actual.nodes()) == expected_nodes + assert set(actual.neighbors(10)) == {11, 14} diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_recognition.py b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_recognition.py new file mode 100644 index 0000000000000000000000000000000000000000..105f5a89e9b10d37d1cc140880a66bc860d2e9f8 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/networkx/algorithms/tree/tests/test_recognition.py @@ -0,0 +1,174 @@ +import pytest + +import networkx as nx + + +class TestTreeRecognition: + graph = nx.Graph + multigraph = nx.MultiGraph + + @classmethod + def setup_class(cls): + cls.T1 = cls.graph() + + cls.T2 = cls.graph() + cls.T2.add_node(1) + + cls.T3 = cls.graph() + cls.T3.add_nodes_from(range(5)) + edges = [(i, i + 1) for i in range(4)] + cls.T3.add_edges_from(edges) + + cls.T5 = cls.multigraph() + cls.T5.add_nodes_from(range(5)) + edges = [(i, i + 1) for i in range(4)] + cls.T5.add_edges_from(edges) + + cls.T6 = cls.graph() + cls.T6.add_nodes_from([6, 7]) + cls.T6.add_edge(6, 7) + + cls.F1 = nx.compose(cls.T6, cls.T3) + + cls.N4 = cls.graph() + cls.N4.add_node(1) + cls.N4.add_edge(1, 1) + + cls.N5 = cls.graph() + cls.N5.add_nodes_from(range(5)) + + cls.N6 = cls.graph() + cls.N6.add_nodes_from(range(3)) + cls.N6.add_edges_from([(0, 1), (1, 2), (2, 0)]) + + cls.NF1 = nx.compose(cls.T6, cls.N6) + + def test_null_tree(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.is_tree(self.graph()) + + def test_null_tree2(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.is_tree(self.multigraph()) + + def test_null_forest(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.is_forest(self.graph()) + + def test_null_forest2(self): + with pytest.raises(nx.NetworkXPointlessConcept): + nx.is_forest(self.multigraph()) + + def test_is_tree(self): + assert nx.is_tree(self.T2) + assert nx.is_tree(self.T3) + assert nx.is_tree(self.T5) + + def test_is_not_tree(self): + assert not nx.is_tree(self.N4) + assert not nx.is_tree(self.N5) + assert not nx.is_tree(self.N6) + + def test_is_forest(self): + assert nx.is_forest(self.T2) + assert nx.is_forest(self.T3) + assert nx.is_forest(self.T5) + assert nx.is_forest(self.F1) + assert nx.is_forest(self.N5) + + def test_is_not_forest(self): + assert not nx.is_forest(self.N4) + assert not nx.is_forest(self.N6) + assert not nx.is_forest(self.NF1) + + +class TestDirectedTreeRecognition(TestTreeRecognition): + graph = nx.DiGraph + multigraph = nx.MultiDiGraph + + +def test_disconnected_graph(): + # https://github.com/networkx/networkx/issues/1144 + G = nx.Graph() + G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)]) + assert not nx.is_tree(G) + + G = nx.DiGraph() + G.add_edges_from([(0, 1), (1, 2), (2, 0), (3, 4)]) + assert not nx.is_tree(G) + + +def test_dag_nontree(): + G = nx.DiGraph() + G.add_edges_from([(0, 1), (0, 2), (1, 2)]) + assert not nx.is_tree(G) + assert nx.is_directed_acyclic_graph(G) + + +def test_multicycle(): + G = nx.MultiDiGraph() + G.add_edges_from([(0, 1), (0, 1)]) + assert not nx.is_tree(G) + assert nx.is_directed_acyclic_graph(G) + + +def test_emptybranch(): + G = nx.DiGraph() + G.add_nodes_from(range(10)) + assert nx.is_branching(G) + assert not nx.is_arborescence(G) + + +def test_is_branching_empty_graph_raises(): + G = nx.DiGraph() + with pytest.raises(nx.NetworkXPointlessConcept, match="G has no nodes."): + nx.is_branching(G) + + +def test_path(): + G = nx.DiGraph() + nx.add_path(G, range(5)) + assert nx.is_branching(G) + assert nx.is_arborescence(G) + + +def test_notbranching1(): + # Acyclic violation. + G = nx.MultiDiGraph() + G.add_nodes_from(range(10)) + G.add_edges_from([(0, 1), (1, 0)]) + assert not nx.is_branching(G) + assert not nx.is_arborescence(G) + + +def test_notbranching2(): + # In-degree violation. + G = nx.MultiDiGraph() + G.add_nodes_from(range(10)) + G.add_edges_from([(0, 1), (0, 2), (3, 2)]) + assert not nx.is_branching(G) + assert not nx.is_arborescence(G) + + +def test_notarborescence1(): + # Not an arborescence due to not spanning. + G = nx.MultiDiGraph() + G.add_nodes_from(range(10)) + G.add_edges_from([(0, 1), (0, 2), (1, 3), (5, 6)]) + assert nx.is_branching(G) + assert not nx.is_arborescence(G) + + +def test_notarborescence2(): + # Not an arborescence due to in-degree violation. + G = nx.MultiDiGraph() + nx.add_path(G, range(5)) + G.add_edge(6, 4) + assert not nx.is_branching(G) + assert not nx.is_arborescence(G) + + +def test_is_arborescense_empty_graph_raises(): + G = nx.DiGraph() + with pytest.raises(nx.NetworkXPointlessConcept, match="G has no nodes."): + nx.is_arborescence(G) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/nvidia_nccl_cu13-2.28.9.dist-info/licenses/License.txt b/URSA/.venv_ursa/lib/python3.12/site-packages/nvidia_nccl_cu13-2.28.9.dist-info/licenses/License.txt new file mode 100644 index 0000000000000000000000000000000000000000..bcd1867a02a6a8c1e592b92e2e50f34e531f2d87 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/nvidia_nccl_cu13-2.28.9.dist-info/licenses/License.txt @@ -0,0 +1,39 @@ + + Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NVIDIA CORPORATION, Lawrence Berkeley National + Laboratory, the U.S. Department of Energy, nor the names of their + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + The U.S. Department of Energy funded the development of this software + under subcontract 7078610 with Lawrence Berkeley National Laboratory. + + +This code also includes files from the NVIDIA Tools Extension SDK project. + +See: + + https://github.com/NVIDIA/NVTX + +for more information and license details. diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f14077002928363de7b08a8922b1a7723d146619 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/zipp.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/zipp.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fb9b3a6c2f9fed5ba9cafc8f6edb49d73ad095ea Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/__pycache__/zipp.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1c93c0e16244d322503b809af604cb183c82040 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/tarfile.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/tarfile.py new file mode 100644 index 0000000000000000000000000000000000000000..a7a9a6e7b9418edae60289dba9bff5d7fb7fcee5 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/backports/tarfile.py @@ -0,0 +1,2900 @@ +#!/usr/bin/env python3 +#------------------------------------------------------------------- +# tarfile.py +#------------------------------------------------------------------- +# Copyright (C) 2002 Lars Gustaebel +# All rights reserved. +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +"""Read from and write to tar format archives. +""" + +version = "0.9.0" +__author__ = "Lars Gust\u00e4bel (lars@gustaebel.de)" +__credits__ = "Gustavo Niemeyer, Niels Gust\u00e4bel, Richard Townsend." + +#--------- +# Imports +#--------- +from builtins import open as bltn_open +import sys +import os +import io +import shutil +import stat +import time +import struct +import copy +import re +import warnings + +try: + import pwd +except ImportError: + pwd = None +try: + import grp +except ImportError: + grp = None + +# os.symlink on Windows prior to 6.0 raises NotImplementedError +# OSError (winerror=1314) will be raised if the caller does not hold the +# SeCreateSymbolicLinkPrivilege privilege +symlink_exception = (AttributeError, NotImplementedError, OSError) + +# from tarfile import * +__all__ = ["TarFile", "TarInfo", "is_tarfile", "TarError", "ReadError", + "CompressionError", "StreamError", "ExtractError", "HeaderError", + "ENCODING", "USTAR_FORMAT", "GNU_FORMAT", "PAX_FORMAT", + "DEFAULT_FORMAT", "open","fully_trusted_filter", "data_filter", + "tar_filter", "FilterError", "AbsoluteLinkError", + "OutsideDestinationError", "SpecialFileError", "AbsolutePathError", + "LinkOutsideDestinationError"] + + +#--------------------------------------------------------- +# tar constants +#--------------------------------------------------------- +NUL = b"\0" # the null character +BLOCKSIZE = 512 # length of processing blocks +RECORDSIZE = BLOCKSIZE * 20 # length of records +GNU_MAGIC = b"ustar \0" # magic gnu tar string +POSIX_MAGIC = b"ustar\x0000" # magic posix tar string + +LENGTH_NAME = 100 # maximum length of a filename +LENGTH_LINK = 100 # maximum length of a linkname +LENGTH_PREFIX = 155 # maximum length of the prefix field + +REGTYPE = b"0" # regular file +AREGTYPE = b"\0" # regular file +LNKTYPE = b"1" # link (inside tarfile) +SYMTYPE = b"2" # symbolic link +CHRTYPE = b"3" # character special device +BLKTYPE = b"4" # block special device +DIRTYPE = b"5" # directory +FIFOTYPE = b"6" # fifo special device +CONTTYPE = b"7" # contiguous file + +GNUTYPE_LONGNAME = b"L" # GNU tar longname +GNUTYPE_LONGLINK = b"K" # GNU tar longlink +GNUTYPE_SPARSE = b"S" # GNU tar sparse file + +XHDTYPE = b"x" # POSIX.1-2001 extended header +XGLTYPE = b"g" # POSIX.1-2001 global header +SOLARIS_XHDTYPE = b"X" # Solaris extended header + +USTAR_FORMAT = 0 # POSIX.1-1988 (ustar) format +GNU_FORMAT = 1 # GNU tar format +PAX_FORMAT = 2 # POSIX.1-2001 (pax) format +DEFAULT_FORMAT = PAX_FORMAT + +#--------------------------------------------------------- +# tarfile constants +#--------------------------------------------------------- +# File types that tarfile supports: +SUPPORTED_TYPES = (REGTYPE, AREGTYPE, LNKTYPE, + SYMTYPE, DIRTYPE, FIFOTYPE, + CONTTYPE, CHRTYPE, BLKTYPE, + GNUTYPE_LONGNAME, GNUTYPE_LONGLINK, + GNUTYPE_SPARSE) + +# File types that will be treated as a regular file. +REGULAR_TYPES = (REGTYPE, AREGTYPE, + CONTTYPE, GNUTYPE_SPARSE) + +# File types that are part of the GNU tar format. +GNU_TYPES = (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK, + GNUTYPE_SPARSE) + +# Fields from a pax header that override a TarInfo attribute. +PAX_FIELDS = ("path", "linkpath", "size", "mtime", + "uid", "gid", "uname", "gname") + +# Fields from a pax header that are affected by hdrcharset. +PAX_NAME_FIELDS = {"path", "linkpath", "uname", "gname"} + +# Fields in a pax header that are numbers, all other fields +# are treated as strings. +PAX_NUMBER_FIELDS = { + "atime": float, + "ctime": float, + "mtime": float, + "uid": int, + "gid": int, + "size": int +} + +#--------------------------------------------------------- +# initialization +#--------------------------------------------------------- +if os.name == "nt": + ENCODING = "utf-8" +else: + ENCODING = sys.getfilesystemencoding() + +#--------------------------------------------------------- +# Some useful functions +#--------------------------------------------------------- + +def stn(s, length, encoding, errors): + """Convert a string to a null-terminated bytes object. + """ + if s is None: + raise ValueError("metadata cannot contain None") + s = s.encode(encoding, errors) + return s[:length] + (length - len(s)) * NUL + +def nts(s, encoding, errors): + """Convert a null-terminated bytes object to a string. + """ + p = s.find(b"\0") + if p != -1: + s = s[:p] + return s.decode(encoding, errors) + +def nti(s): + """Convert a number field to a python number. + """ + # There are two possible encodings for a number field, see + # itn() below. + if s[0] in (0o200, 0o377): + n = 0 + for i in range(len(s) - 1): + n <<= 8 + n += s[i + 1] + if s[0] == 0o377: + n = -(256 ** (len(s) - 1) - n) + else: + try: + s = nts(s, "ascii", "strict") + n = int(s.strip() or "0", 8) + except ValueError: + raise InvalidHeaderError("invalid header") + return n + +def itn(n, digits=8, format=DEFAULT_FORMAT): + """Convert a python number to a number field. + """ + # POSIX 1003.1-1988 requires numbers to be encoded as a string of + # octal digits followed by a null-byte, this allows values up to + # (8**(digits-1))-1. GNU tar allows storing numbers greater than + # that if necessary. A leading 0o200 or 0o377 byte indicate this + # particular encoding, the following digits-1 bytes are a big-endian + # base-256 representation. This allows values up to (256**(digits-1))-1. + # A 0o200 byte indicates a positive number, a 0o377 byte a negative + # number. + original_n = n + n = int(n) + if 0 <= n < 8 ** (digits - 1): + s = bytes("%0*o" % (digits - 1, n), "ascii") + NUL + elif format == GNU_FORMAT and -256 ** (digits - 1) <= n < 256 ** (digits - 1): + if n >= 0: + s = bytearray([0o200]) + else: + s = bytearray([0o377]) + n = 256 ** digits + n + + for i in range(digits - 1): + s.insert(1, n & 0o377) + n >>= 8 + else: + raise ValueError("overflow in number field") + + return s + +def calc_chksums(buf): + """Calculate the checksum for a member's header by summing up all + characters except for the chksum field which is treated as if + it was filled with spaces. According to the GNU tar sources, + some tars (Sun and NeXT) calculate chksum with signed char, + which will be different if there are chars in the buffer with + the high bit set. So we calculate two checksums, unsigned and + signed. + """ + unsigned_chksum = 256 + sum(struct.unpack_from("148B8x356B", buf)) + signed_chksum = 256 + sum(struct.unpack_from("148b8x356b", buf)) + return unsigned_chksum, signed_chksum + +def copyfileobj(src, dst, length=None, exception=OSError, bufsize=None): + """Copy length bytes from fileobj src to fileobj dst. + If length is None, copy the entire content. + """ + bufsize = bufsize or 16 * 1024 + if length == 0: + return + if length is None: + shutil.copyfileobj(src, dst, bufsize) + return + + blocks, remainder = divmod(length, bufsize) + for b in range(blocks): + buf = src.read(bufsize) + if len(buf) < bufsize: + raise exception("unexpected end of data") + dst.write(buf) + + if remainder != 0: + buf = src.read(remainder) + if len(buf) < remainder: + raise exception("unexpected end of data") + dst.write(buf) + return + +def _safe_print(s): + encoding = getattr(sys.stdout, 'encoding', None) + if encoding is not None: + s = s.encode(encoding, 'backslashreplace').decode(encoding) + print(s, end=' ') + + +class TarError(Exception): + """Base exception.""" + pass +class ExtractError(TarError): + """General exception for extract errors.""" + pass +class ReadError(TarError): + """Exception for unreadable tar archives.""" + pass +class CompressionError(TarError): + """Exception for unavailable compression methods.""" + pass +class StreamError(TarError): + """Exception for unsupported operations on stream-like TarFiles.""" + pass +class HeaderError(TarError): + """Base exception for header errors.""" + pass +class EmptyHeaderError(HeaderError): + """Exception for empty headers.""" + pass +class TruncatedHeaderError(HeaderError): + """Exception for truncated headers.""" + pass +class EOFHeaderError(HeaderError): + """Exception for end of file headers.""" + pass +class InvalidHeaderError(HeaderError): + """Exception for invalid headers.""" + pass +class SubsequentHeaderError(HeaderError): + """Exception for missing and invalid extended headers.""" + pass + +#--------------------------- +# internal stream interface +#--------------------------- +class _LowLevelFile: + """Low-level file object. Supports reading and writing. + It is used instead of a regular file object for streaming + access. + """ + + def __init__(self, name, mode): + mode = { + "r": os.O_RDONLY, + "w": os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + }[mode] + if hasattr(os, "O_BINARY"): + mode |= os.O_BINARY + self.fd = os.open(name, mode, 0o666) + + def close(self): + os.close(self.fd) + + def read(self, size): + return os.read(self.fd, size) + + def write(self, s): + os.write(self.fd, s) + +class _Stream: + """Class that serves as an adapter between TarFile and + a stream-like object. The stream-like object only + needs to have a read() or write() method that works with bytes, + and the method is accessed blockwise. + Use of gzip or bzip2 compression is possible. + A stream-like object could be for example: sys.stdin.buffer, + sys.stdout.buffer, a socket, a tape device etc. + + _Stream is intended to be used only internally. + """ + + def __init__(self, name, mode, comptype, fileobj, bufsize, + compresslevel): + """Construct a _Stream object. + """ + self._extfileobj = True + if fileobj is None: + fileobj = _LowLevelFile(name, mode) + self._extfileobj = False + + if comptype == '*': + # Enable transparent compression detection for the + # stream interface + fileobj = _StreamProxy(fileobj) + comptype = fileobj.getcomptype() + + self.name = name or "" + self.mode = mode + self.comptype = comptype + self.fileobj = fileobj + self.bufsize = bufsize + self.buf = b"" + self.pos = 0 + self.closed = False + + try: + if comptype == "gz": + try: + import zlib + except ImportError: + raise CompressionError("zlib module is not available") from None + self.zlib = zlib + self.crc = zlib.crc32(b"") + if mode == "r": + self.exception = zlib.error + self._init_read_gz() + else: + self._init_write_gz(compresslevel) + + elif comptype == "bz2": + try: + import bz2 + except ImportError: + raise CompressionError("bz2 module is not available") from None + if mode == "r": + self.dbuf = b"" + self.cmp = bz2.BZ2Decompressor() + self.exception = OSError + else: + self.cmp = bz2.BZ2Compressor(compresslevel) + + elif comptype == "xz": + try: + import lzma + except ImportError: + raise CompressionError("lzma module is not available") from None + if mode == "r": + self.dbuf = b"" + self.cmp = lzma.LZMADecompressor() + self.exception = lzma.LZMAError + else: + self.cmp = lzma.LZMACompressor() + + elif comptype != "tar": + raise CompressionError("unknown compression type %r" % comptype) + + except: + if not self._extfileobj: + self.fileobj.close() + self.closed = True + raise + + def __del__(self): + if hasattr(self, "closed") and not self.closed: + self.close() + + def _init_write_gz(self, compresslevel): + """Initialize for writing with gzip compression. + """ + self.cmp = self.zlib.compressobj(compresslevel, + self.zlib.DEFLATED, + -self.zlib.MAX_WBITS, + self.zlib.DEF_MEM_LEVEL, + 0) + timestamp = struct.pack(" self.bufsize: + self.fileobj.write(self.buf[:self.bufsize]) + self.buf = self.buf[self.bufsize:] + + def close(self): + """Close the _Stream object. No operation should be + done on it afterwards. + """ + if self.closed: + return + + self.closed = True + try: + if self.mode == "w" and self.comptype != "tar": + self.buf += self.cmp.flush() + + if self.mode == "w" and self.buf: + self.fileobj.write(self.buf) + self.buf = b"" + if self.comptype == "gz": + self.fileobj.write(struct.pack("= 0: + blocks, remainder = divmod(pos - self.pos, self.bufsize) + for i in range(blocks): + self.read(self.bufsize) + self.read(remainder) + else: + raise StreamError("seeking backwards is not allowed") + return self.pos + + def read(self, size): + """Return the next size number of bytes from the stream.""" + assert size is not None + buf = self._read(size) + self.pos += len(buf) + return buf + + def _read(self, size): + """Return size bytes from the stream. + """ + if self.comptype == "tar": + return self.__read(size) + + c = len(self.dbuf) + t = [self.dbuf] + while c < size: + # Skip underlying buffer to avoid unaligned double buffering. + if self.buf: + buf = self.buf + self.buf = b"" + else: + buf = self.fileobj.read(self.bufsize) + if not buf: + break + try: + buf = self.cmp.decompress(buf) + except self.exception as e: + raise ReadError("invalid compressed data") from e + t.append(buf) + c += len(buf) + t = b"".join(t) + self.dbuf = t[size:] + return t[:size] + + def __read(self, size): + """Return size bytes from stream. If internal buffer is empty, + read another block from the stream. + """ + c = len(self.buf) + t = [self.buf] + while c < size: + buf = self.fileobj.read(self.bufsize) + if not buf: + break + t.append(buf) + c += len(buf) + t = b"".join(t) + self.buf = t[size:] + return t[:size] +# class _Stream + +class _StreamProxy(object): + """Small proxy class that enables transparent compression + detection for the Stream interface (mode 'r|*'). + """ + + def __init__(self, fileobj): + self.fileobj = fileobj + self.buf = self.fileobj.read(BLOCKSIZE) + + def read(self, size): + self.read = self.fileobj.read + return self.buf + + def getcomptype(self): + if self.buf.startswith(b"\x1f\x8b\x08"): + return "gz" + elif self.buf[0:3] == b"BZh" and self.buf[4:10] == b"1AY&SY": + return "bz2" + elif self.buf.startswith((b"\x5d\x00\x00\x80", b"\xfd7zXZ")): + return "xz" + else: + return "tar" + + def close(self): + self.fileobj.close() +# class StreamProxy + +#------------------------ +# Extraction file object +#------------------------ +class _FileInFile(object): + """A thin wrapper around an existing file object that + provides a part of its data as an individual file + object. + """ + + def __init__(self, fileobj, offset, size, name, blockinfo=None): + self.fileobj = fileobj + self.offset = offset + self.size = size + self.position = 0 + self.name = name + self.closed = False + + if blockinfo is None: + blockinfo = [(0, size)] + + # Construct a map with data and zero blocks. + self.map_index = 0 + self.map = [] + lastpos = 0 + realpos = self.offset + for offset, size in blockinfo: + if offset > lastpos: + self.map.append((False, lastpos, offset, None)) + self.map.append((True, offset, offset + size, realpos)) + realpos += size + lastpos = offset + size + if lastpos < self.size: + self.map.append((False, lastpos, self.size, None)) + + def flush(self): + pass + + def readable(self): + return True + + def writable(self): + return False + + def seekable(self): + return self.fileobj.seekable() + + def tell(self): + """Return the current file position. + """ + return self.position + + def seek(self, position, whence=io.SEEK_SET): + """Seek to a position in the file. + """ + if whence == io.SEEK_SET: + self.position = min(max(position, 0), self.size) + elif whence == io.SEEK_CUR: + if position < 0: + self.position = max(self.position + position, 0) + else: + self.position = min(self.position + position, self.size) + elif whence == io.SEEK_END: + self.position = max(min(self.size + position, self.size), 0) + else: + raise ValueError("Invalid argument") + return self.position + + def read(self, size=None): + """Read data from the file. + """ + if size is None: + size = self.size - self.position + else: + size = min(size, self.size - self.position) + + buf = b"" + while size > 0: + while True: + data, start, stop, offset = self.map[self.map_index] + if start <= self.position < stop: + break + else: + self.map_index += 1 + if self.map_index == len(self.map): + self.map_index = 0 + length = min(size, stop - self.position) + if data: + self.fileobj.seek(offset + (self.position - start)) + b = self.fileobj.read(length) + if len(b) != length: + raise ReadError("unexpected end of data") + buf += b + else: + buf += NUL * length + size -= length + self.position += length + return buf + + def readinto(self, b): + buf = self.read(len(b)) + b[:len(buf)] = buf + return len(buf) + + def close(self): + self.closed = True +#class _FileInFile + +class ExFileObject(io.BufferedReader): + + def __init__(self, tarfile, tarinfo): + fileobj = _FileInFile(tarfile.fileobj, tarinfo.offset_data, + tarinfo.size, tarinfo.name, tarinfo.sparse) + super().__init__(fileobj) +#class ExFileObject + + +#----------------------------- +# extraction filters (PEP 706) +#----------------------------- + +class FilterError(TarError): + pass + +class AbsolutePathError(FilterError): + def __init__(self, tarinfo): + self.tarinfo = tarinfo + super().__init__(f'member {tarinfo.name!r} has an absolute path') + +class OutsideDestinationError(FilterError): + def __init__(self, tarinfo, path): + self.tarinfo = tarinfo + self._path = path + super().__init__(f'{tarinfo.name!r} would be extracted to {path!r}, ' + + 'which is outside the destination') + +class SpecialFileError(FilterError): + def __init__(self, tarinfo): + self.tarinfo = tarinfo + super().__init__(f'{tarinfo.name!r} is a special file') + +class AbsoluteLinkError(FilterError): + def __init__(self, tarinfo): + self.tarinfo = tarinfo + super().__init__(f'{tarinfo.name!r} is a link to an absolute path') + +class LinkOutsideDestinationError(FilterError): + def __init__(self, tarinfo, path): + self.tarinfo = tarinfo + self._path = path + super().__init__(f'{tarinfo.name!r} would link to {path!r}, ' + + 'which is outside the destination') + +def _get_filtered_attrs(member, dest_path, for_data=True): + new_attrs = {} + name = member.name + dest_path = os.path.realpath(dest_path) + # Strip leading / (tar's directory separator) from filenames. + # Include os.sep (target OS directory separator) as well. + if name.startswith(('/', os.sep)): + name = new_attrs['name'] = member.path.lstrip('/' + os.sep) + if os.path.isabs(name): + # Path is absolute even after stripping. + # For example, 'C:/foo' on Windows. + raise AbsolutePathError(member) + # Ensure we stay in the destination + target_path = os.path.realpath(os.path.join(dest_path, name)) + if os.path.commonpath([target_path, dest_path]) != dest_path: + raise OutsideDestinationError(member, target_path) + # Limit permissions (no high bits, and go-w) + mode = member.mode + if mode is not None: + # Strip high bits & group/other write bits + mode = mode & 0o755 + if for_data: + # For data, handle permissions & file types + if member.isreg() or member.islnk(): + if not mode & 0o100: + # Clear executable bits if not executable by user + mode &= ~0o111 + # Ensure owner can read & write + mode |= 0o600 + elif member.isdir() or member.issym(): + # Ignore mode for directories & symlinks + mode = None + else: + # Reject special files + raise SpecialFileError(member) + if mode != member.mode: + new_attrs['mode'] = mode + if for_data: + # Ignore ownership for 'data' + if member.uid is not None: + new_attrs['uid'] = None + if member.gid is not None: + new_attrs['gid'] = None + if member.uname is not None: + new_attrs['uname'] = None + if member.gname is not None: + new_attrs['gname'] = None + # Check link destination for 'data' + if member.islnk() or member.issym(): + if os.path.isabs(member.linkname): + raise AbsoluteLinkError(member) + if member.issym(): + target_path = os.path.join(dest_path, + os.path.dirname(name), + member.linkname) + else: + target_path = os.path.join(dest_path, + member.linkname) + target_path = os.path.realpath(target_path) + if os.path.commonpath([target_path, dest_path]) != dest_path: + raise LinkOutsideDestinationError(member, target_path) + return new_attrs + +def fully_trusted_filter(member, dest_path): + return member + +def tar_filter(member, dest_path): + new_attrs = _get_filtered_attrs(member, dest_path, False) + if new_attrs: + return member.replace(**new_attrs, deep=False) + return member + +def data_filter(member, dest_path): + new_attrs = _get_filtered_attrs(member, dest_path, True) + if new_attrs: + return member.replace(**new_attrs, deep=False) + return member + +_NAMED_FILTERS = { + "fully_trusted": fully_trusted_filter, + "tar": tar_filter, + "data": data_filter, +} + +#------------------ +# Exported Classes +#------------------ + +# Sentinel for replace() defaults, meaning "don't change the attribute" +_KEEP = object() + +class TarInfo(object): + """Informational class which holds the details about an + archive member given by a tar header block. + TarInfo objects are returned by TarFile.getmember(), + TarFile.getmembers() and TarFile.gettarinfo() and are + usually created internally. + """ + + __slots__ = dict( + name = 'Name of the archive member.', + mode = 'Permission bits.', + uid = 'User ID of the user who originally stored this member.', + gid = 'Group ID of the user who originally stored this member.', + size = 'Size in bytes.', + mtime = 'Time of last modification.', + chksum = 'Header checksum.', + type = ('File type. type is usually one of these constants: ' + 'REGTYPE, AREGTYPE, LNKTYPE, SYMTYPE, DIRTYPE, FIFOTYPE, ' + 'CONTTYPE, CHRTYPE, BLKTYPE, GNUTYPE_SPARSE.'), + linkname = ('Name of the target file name, which is only present ' + 'in TarInfo objects of type LNKTYPE and SYMTYPE.'), + uname = 'User name.', + gname = 'Group name.', + devmajor = 'Device major number.', + devminor = 'Device minor number.', + offset = 'The tar header starts here.', + offset_data = "The file's data starts here.", + pax_headers = ('A dictionary containing key-value pairs of an ' + 'associated pax extended header.'), + sparse = 'Sparse member information.', + tarfile = None, + _sparse_structs = None, + _link_target = None, + ) + + def __init__(self, name=""): + """Construct a TarInfo object. name is the optional name + of the member. + """ + self.name = name # member name + self.mode = 0o644 # file permissions + self.uid = 0 # user id + self.gid = 0 # group id + self.size = 0 # file size + self.mtime = 0 # modification time + self.chksum = 0 # header checksum + self.type = REGTYPE # member type + self.linkname = "" # link name + self.uname = "" # user name + self.gname = "" # group name + self.devmajor = 0 # device major number + self.devminor = 0 # device minor number + + self.offset = 0 # the tar header starts here + self.offset_data = 0 # the file's data starts here + + self.sparse = None # sparse member information + self.pax_headers = {} # pax header information + + @property + def path(self): + 'In pax headers, "name" is called "path".' + return self.name + + @path.setter + def path(self, name): + self.name = name + + @property + def linkpath(self): + 'In pax headers, "linkname" is called "linkpath".' + return self.linkname + + @linkpath.setter + def linkpath(self, linkname): + self.linkname = linkname + + def __repr__(self): + return "<%s %r at %#x>" % (self.__class__.__name__,self.name,id(self)) + + def replace(self, *, + name=_KEEP, mtime=_KEEP, mode=_KEEP, linkname=_KEEP, + uid=_KEEP, gid=_KEEP, uname=_KEEP, gname=_KEEP, + deep=True, _KEEP=_KEEP): + """Return a deep copy of self with the given attributes replaced. + """ + if deep: + result = copy.deepcopy(self) + else: + result = copy.copy(self) + if name is not _KEEP: + result.name = name + if mtime is not _KEEP: + result.mtime = mtime + if mode is not _KEEP: + result.mode = mode + if linkname is not _KEEP: + result.linkname = linkname + if uid is not _KEEP: + result.uid = uid + if gid is not _KEEP: + result.gid = gid + if uname is not _KEEP: + result.uname = uname + if gname is not _KEEP: + result.gname = gname + return result + + def get_info(self): + """Return the TarInfo's attributes as a dictionary. + """ + if self.mode is None: + mode = None + else: + mode = self.mode & 0o7777 + info = { + "name": self.name, + "mode": mode, + "uid": self.uid, + "gid": self.gid, + "size": self.size, + "mtime": self.mtime, + "chksum": self.chksum, + "type": self.type, + "linkname": self.linkname, + "uname": self.uname, + "gname": self.gname, + "devmajor": self.devmajor, + "devminor": self.devminor + } + + if info["type"] == DIRTYPE and not info["name"].endswith("/"): + info["name"] += "/" + + return info + + def tobuf(self, format=DEFAULT_FORMAT, encoding=ENCODING, errors="surrogateescape"): + """Return a tar header as a string of 512 byte blocks. + """ + info = self.get_info() + for name, value in info.items(): + if value is None: + raise ValueError("%s may not be None" % name) + + if format == USTAR_FORMAT: + return self.create_ustar_header(info, encoding, errors) + elif format == GNU_FORMAT: + return self.create_gnu_header(info, encoding, errors) + elif format == PAX_FORMAT: + return self.create_pax_header(info, encoding) + else: + raise ValueError("invalid format") + + def create_ustar_header(self, info, encoding, errors): + """Return the object as a ustar header block. + """ + info["magic"] = POSIX_MAGIC + + if len(info["linkname"].encode(encoding, errors)) > LENGTH_LINK: + raise ValueError("linkname is too long") + + if len(info["name"].encode(encoding, errors)) > LENGTH_NAME: + info["prefix"], info["name"] = self._posix_split_name(info["name"], encoding, errors) + + return self._create_header(info, USTAR_FORMAT, encoding, errors) + + def create_gnu_header(self, info, encoding, errors): + """Return the object as a GNU header block sequence. + """ + info["magic"] = GNU_MAGIC + + buf = b"" + if len(info["linkname"].encode(encoding, errors)) > LENGTH_LINK: + buf += self._create_gnu_long_header(info["linkname"], GNUTYPE_LONGLINK, encoding, errors) + + if len(info["name"].encode(encoding, errors)) > LENGTH_NAME: + buf += self._create_gnu_long_header(info["name"], GNUTYPE_LONGNAME, encoding, errors) + + return buf + self._create_header(info, GNU_FORMAT, encoding, errors) + + def create_pax_header(self, info, encoding): + """Return the object as a ustar header block. If it cannot be + represented this way, prepend a pax extended header sequence + with supplement information. + """ + info["magic"] = POSIX_MAGIC + pax_headers = self.pax_headers.copy() + + # Test string fields for values that exceed the field length or cannot + # be represented in ASCII encoding. + for name, hname, length in ( + ("name", "path", LENGTH_NAME), ("linkname", "linkpath", LENGTH_LINK), + ("uname", "uname", 32), ("gname", "gname", 32)): + + if hname in pax_headers: + # The pax header has priority. + continue + + # Try to encode the string as ASCII. + try: + info[name].encode("ascii", "strict") + except UnicodeEncodeError: + pax_headers[hname] = info[name] + continue + + if len(info[name]) > length: + pax_headers[hname] = info[name] + + # Test number fields for values that exceed the field limit or values + # that like to be stored as float. + for name, digits in (("uid", 8), ("gid", 8), ("size", 12), ("mtime", 12)): + needs_pax = False + + val = info[name] + val_is_float = isinstance(val, float) + val_int = round(val) if val_is_float else val + if not 0 <= val_int < 8 ** (digits - 1): + # Avoid overflow. + info[name] = 0 + needs_pax = True + elif val_is_float: + # Put rounded value in ustar header, and full + # precision value in pax header. + info[name] = val_int + needs_pax = True + + # The existing pax header has priority. + if needs_pax and name not in pax_headers: + pax_headers[name] = str(val) + + # Create a pax extended header if necessary. + if pax_headers: + buf = self._create_pax_generic_header(pax_headers, XHDTYPE, encoding) + else: + buf = b"" + + return buf + self._create_header(info, USTAR_FORMAT, "ascii", "replace") + + @classmethod + def create_pax_global_header(cls, pax_headers): + """Return the object as a pax global header block sequence. + """ + return cls._create_pax_generic_header(pax_headers, XGLTYPE, "utf-8") + + def _posix_split_name(self, name, encoding, errors): + """Split a name longer than 100 chars into a prefix + and a name part. + """ + components = name.split("/") + for i in range(1, len(components)): + prefix = "/".join(components[:i]) + name = "/".join(components[i:]) + if len(prefix.encode(encoding, errors)) <= LENGTH_PREFIX and \ + len(name.encode(encoding, errors)) <= LENGTH_NAME: + break + else: + raise ValueError("name is too long") + + return prefix, name + + @staticmethod + def _create_header(info, format, encoding, errors): + """Return a header block. info is a dictionary with file + information, format must be one of the *_FORMAT constants. + """ + has_device_fields = info.get("type") in (CHRTYPE, BLKTYPE) + if has_device_fields: + devmajor = itn(info.get("devmajor", 0), 8, format) + devminor = itn(info.get("devminor", 0), 8, format) + else: + devmajor = stn("", 8, encoding, errors) + devminor = stn("", 8, encoding, errors) + + # None values in metadata should cause ValueError. + # itn()/stn() do this for all fields except type. + filetype = info.get("type", REGTYPE) + if filetype is None: + raise ValueError("TarInfo.type must not be None") + + parts = [ + stn(info.get("name", ""), 100, encoding, errors), + itn(info.get("mode", 0) & 0o7777, 8, format), + itn(info.get("uid", 0), 8, format), + itn(info.get("gid", 0), 8, format), + itn(info.get("size", 0), 12, format), + itn(info.get("mtime", 0), 12, format), + b" ", # checksum field + filetype, + stn(info.get("linkname", ""), 100, encoding, errors), + info.get("magic", POSIX_MAGIC), + stn(info.get("uname", ""), 32, encoding, errors), + stn(info.get("gname", ""), 32, encoding, errors), + devmajor, + devminor, + stn(info.get("prefix", ""), 155, encoding, errors) + ] + + buf = struct.pack("%ds" % BLOCKSIZE, b"".join(parts)) + chksum = calc_chksums(buf[-BLOCKSIZE:])[0] + buf = buf[:-364] + bytes("%06o\0" % chksum, "ascii") + buf[-357:] + return buf + + @staticmethod + def _create_payload(payload): + """Return the string payload filled with zero bytes + up to the next 512 byte border. + """ + blocks, remainder = divmod(len(payload), BLOCKSIZE) + if remainder > 0: + payload += (BLOCKSIZE - remainder) * NUL + return payload + + @classmethod + def _create_gnu_long_header(cls, name, type, encoding, errors): + """Return a GNUTYPE_LONGNAME or GNUTYPE_LONGLINK sequence + for name. + """ + name = name.encode(encoding, errors) + NUL + + info = {} + info["name"] = "././@LongLink" + info["type"] = type + info["size"] = len(name) + info["magic"] = GNU_MAGIC + + # create extended header + name blocks. + return cls._create_header(info, USTAR_FORMAT, encoding, errors) + \ + cls._create_payload(name) + + @classmethod + def _create_pax_generic_header(cls, pax_headers, type, encoding): + """Return a POSIX.1-2008 extended or global header sequence + that contains a list of keyword, value pairs. The values + must be strings. + """ + # Check if one of the fields contains surrogate characters and thereby + # forces hdrcharset=BINARY, see _proc_pax() for more information. + binary = False + for keyword, value in pax_headers.items(): + try: + value.encode("utf-8", "strict") + except UnicodeEncodeError: + binary = True + break + + records = b"" + if binary: + # Put the hdrcharset field at the beginning of the header. + records += b"21 hdrcharset=BINARY\n" + + for keyword, value in pax_headers.items(): + keyword = keyword.encode("utf-8") + if binary: + # Try to restore the original byte representation of `value'. + # Needless to say, that the encoding must match the string. + value = value.encode(encoding, "surrogateescape") + else: + value = value.encode("utf-8") + + l = len(keyword) + len(value) + 3 # ' ' + '=' + '\n' + n = p = 0 + while True: + n = l + len(str(p)) + if n == p: + break + p = n + records += bytes(str(p), "ascii") + b" " + keyword + b"=" + value + b"\n" + + # We use a hardcoded "././@PaxHeader" name like star does + # instead of the one that POSIX recommends. + info = {} + info["name"] = "././@PaxHeader" + info["type"] = type + info["size"] = len(records) + info["magic"] = POSIX_MAGIC + + # Create pax header + record blocks. + return cls._create_header(info, USTAR_FORMAT, "ascii", "replace") + \ + cls._create_payload(records) + + @classmethod + def frombuf(cls, buf, encoding, errors): + """Construct a TarInfo object from a 512 byte bytes object. + """ + if len(buf) == 0: + raise EmptyHeaderError("empty header") + if len(buf) != BLOCKSIZE: + raise TruncatedHeaderError("truncated header") + if buf.count(NUL) == BLOCKSIZE: + raise EOFHeaderError("end of file header") + + chksum = nti(buf[148:156]) + if chksum not in calc_chksums(buf): + raise InvalidHeaderError("bad checksum") + + obj = cls() + obj.name = nts(buf[0:100], encoding, errors) + obj.mode = nti(buf[100:108]) + obj.uid = nti(buf[108:116]) + obj.gid = nti(buf[116:124]) + obj.size = nti(buf[124:136]) + obj.mtime = nti(buf[136:148]) + obj.chksum = chksum + obj.type = buf[156:157] + obj.linkname = nts(buf[157:257], encoding, errors) + obj.uname = nts(buf[265:297], encoding, errors) + obj.gname = nts(buf[297:329], encoding, errors) + obj.devmajor = nti(buf[329:337]) + obj.devminor = nti(buf[337:345]) + prefix = nts(buf[345:500], encoding, errors) + + # Old V7 tar format represents a directory as a regular + # file with a trailing slash. + if obj.type == AREGTYPE and obj.name.endswith("/"): + obj.type = DIRTYPE + + # The old GNU sparse format occupies some of the unused + # space in the buffer for up to 4 sparse structures. + # Save them for later processing in _proc_sparse(). + if obj.type == GNUTYPE_SPARSE: + pos = 386 + structs = [] + for i in range(4): + try: + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) + except ValueError: + break + structs.append((offset, numbytes)) + pos += 24 + isextended = bool(buf[482]) + origsize = nti(buf[483:495]) + obj._sparse_structs = (structs, isextended, origsize) + + # Remove redundant slashes from directories. + if obj.isdir(): + obj.name = obj.name.rstrip("/") + + # Reconstruct a ustar longname. + if prefix and obj.type not in GNU_TYPES: + obj.name = prefix + "/" + obj.name + return obj + + @classmethod + def fromtarfile(cls, tarfile): + """Return the next TarInfo object from TarFile object + tarfile. + """ + buf = tarfile.fileobj.read(BLOCKSIZE) + obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors) + obj.offset = tarfile.fileobj.tell() - BLOCKSIZE + return obj._proc_member(tarfile) + + #-------------------------------------------------------------------------- + # The following are methods that are called depending on the type of a + # member. The entry point is _proc_member() which can be overridden in a + # subclass to add custom _proc_*() methods. A _proc_*() method MUST + # implement the following + # operations: + # 1. Set self.offset_data to the position where the data blocks begin, + # if there is data that follows. + # 2. Set tarfile.offset to the position where the next member's header will + # begin. + # 3. Return self or another valid TarInfo object. + def _proc_member(self, tarfile): + """Choose the right processing method depending on + the type and call it. + """ + if self.type in (GNUTYPE_LONGNAME, GNUTYPE_LONGLINK): + return self._proc_gnulong(tarfile) + elif self.type == GNUTYPE_SPARSE: + return self._proc_sparse(tarfile) + elif self.type in (XHDTYPE, XGLTYPE, SOLARIS_XHDTYPE): + return self._proc_pax(tarfile) + else: + return self._proc_builtin(tarfile) + + def _proc_builtin(self, tarfile): + """Process a builtin type or an unknown type which + will be treated as a regular file. + """ + self.offset_data = tarfile.fileobj.tell() + offset = self.offset_data + if self.isreg() or self.type not in SUPPORTED_TYPES: + # Skip the following data blocks. + offset += self._block(self.size) + tarfile.offset = offset + + # Patch the TarInfo object with saved global + # header information. + self._apply_pax_info(tarfile.pax_headers, tarfile.encoding, tarfile.errors) + + # Remove redundant slashes from directories. This is to be consistent + # with frombuf(). + if self.isdir(): + self.name = self.name.rstrip("/") + + return self + + def _proc_gnulong(self, tarfile): + """Process the blocks that hold a GNU longname + or longlink member. + """ + buf = tarfile.fileobj.read(self._block(self.size)) + + # Fetch the next header and process it. + try: + next = self.fromtarfile(tarfile) + except HeaderError as e: + raise SubsequentHeaderError(str(e)) from None + + # Patch the TarInfo object from the next header with + # the longname information. + next.offset = self.offset + if self.type == GNUTYPE_LONGNAME: + next.name = nts(buf, tarfile.encoding, tarfile.errors) + elif self.type == GNUTYPE_LONGLINK: + next.linkname = nts(buf, tarfile.encoding, tarfile.errors) + + # Remove redundant slashes from directories. This is to be consistent + # with frombuf(). + if next.isdir(): + next.name = next.name.removesuffix("/") + + return next + + def _proc_sparse(self, tarfile): + """Process a GNU sparse header plus extra headers. + """ + # We already collected some sparse structures in frombuf(). + structs, isextended, origsize = self._sparse_structs + del self._sparse_structs + + # Collect sparse structures from extended header blocks. + while isextended: + buf = tarfile.fileobj.read(BLOCKSIZE) + pos = 0 + for i in range(21): + try: + offset = nti(buf[pos:pos + 12]) + numbytes = nti(buf[pos + 12:pos + 24]) + except ValueError: + break + if offset and numbytes: + structs.append((offset, numbytes)) + pos += 24 + isextended = bool(buf[504]) + self.sparse = structs + + self.offset_data = tarfile.fileobj.tell() + tarfile.offset = self.offset_data + self._block(self.size) + self.size = origsize + return self + + def _proc_pax(self, tarfile): + """Process an extended or global header as described in + POSIX.1-2008. + """ + # Read the header information. + buf = tarfile.fileobj.read(self._block(self.size)) + + # A pax header stores supplemental information for either + # the following file (extended) or all following files + # (global). + if self.type == XGLTYPE: + pax_headers = tarfile.pax_headers + else: + pax_headers = tarfile.pax_headers.copy() + + # Check if the pax header contains a hdrcharset field. This tells us + # the encoding of the path, linkpath, uname and gname fields. Normally, + # these fields are UTF-8 encoded but since POSIX.1-2008 tar + # implementations are allowed to store them as raw binary strings if + # the translation to UTF-8 fails. + match = re.search(br"\d+ hdrcharset=([^\n]+)\n", buf) + if match is not None: + pax_headers["hdrcharset"] = match.group(1).decode("utf-8") + + # For the time being, we don't care about anything other than "BINARY". + # The only other value that is currently allowed by the standard is + # "ISO-IR 10646 2000 UTF-8" in other words UTF-8. + hdrcharset = pax_headers.get("hdrcharset") + if hdrcharset == "BINARY": + encoding = tarfile.encoding + else: + encoding = "utf-8" + + # Parse pax header information. A record looks like that: + # "%d %s=%s\n" % (length, keyword, value). length is the size + # of the complete record including the length field itself and + # the newline. keyword and value are both UTF-8 encoded strings. + regex = re.compile(br"(\d+) ([^=]+)=") + pos = 0 + while match := regex.match(buf, pos): + length, keyword = match.groups() + length = int(length) + if length == 0: + raise InvalidHeaderError("invalid header") + value = buf[match.end(2) + 1:match.start(1) + length - 1] + + # Normally, we could just use "utf-8" as the encoding and "strict" + # as the error handler, but we better not take the risk. For + # example, GNU tar <= 1.23 is known to store filenames it cannot + # translate to UTF-8 as raw strings (unfortunately without a + # hdrcharset=BINARY header). + # We first try the strict standard encoding, and if that fails we + # fall back on the user's encoding and error handler. + keyword = self._decode_pax_field(keyword, "utf-8", "utf-8", + tarfile.errors) + if keyword in PAX_NAME_FIELDS: + value = self._decode_pax_field(value, encoding, tarfile.encoding, + tarfile.errors) + else: + value = self._decode_pax_field(value, "utf-8", "utf-8", + tarfile.errors) + + pax_headers[keyword] = value + pos += length + + # Fetch the next header. + try: + next = self.fromtarfile(tarfile) + except HeaderError as e: + raise SubsequentHeaderError(str(e)) from None + + # Process GNU sparse information. + if "GNU.sparse.map" in pax_headers: + # GNU extended sparse format version 0.1. + self._proc_gnusparse_01(next, pax_headers) + + elif "GNU.sparse.size" in pax_headers: + # GNU extended sparse format version 0.0. + self._proc_gnusparse_00(next, pax_headers, buf) + + elif pax_headers.get("GNU.sparse.major") == "1" and pax_headers.get("GNU.sparse.minor") == "0": + # GNU extended sparse format version 1.0. + self._proc_gnusparse_10(next, pax_headers, tarfile) + + if self.type in (XHDTYPE, SOLARIS_XHDTYPE): + # Patch the TarInfo object with the extended header info. + next._apply_pax_info(pax_headers, tarfile.encoding, tarfile.errors) + next.offset = self.offset + + if "size" in pax_headers: + # If the extended header replaces the size field, + # we need to recalculate the offset where the next + # header starts. + offset = next.offset_data + if next.isreg() or next.type not in SUPPORTED_TYPES: + offset += next._block(next.size) + tarfile.offset = offset + + return next + + def _proc_gnusparse_00(self, next, pax_headers, buf): + """Process a GNU tar extended sparse header, version 0.0. + """ + offsets = [] + for match in re.finditer(br"\d+ GNU.sparse.offset=(\d+)\n", buf): + offsets.append(int(match.group(1))) + numbytes = [] + for match in re.finditer(br"\d+ GNU.sparse.numbytes=(\d+)\n", buf): + numbytes.append(int(match.group(1))) + next.sparse = list(zip(offsets, numbytes)) + + def _proc_gnusparse_01(self, next, pax_headers): + """Process a GNU tar extended sparse header, version 0.1. + """ + sparse = [int(x) for x in pax_headers["GNU.sparse.map"].split(",")] + next.sparse = list(zip(sparse[::2], sparse[1::2])) + + def _proc_gnusparse_10(self, next, pax_headers, tarfile): + """Process a GNU tar extended sparse header, version 1.0. + """ + fields = None + sparse = [] + buf = tarfile.fileobj.read(BLOCKSIZE) + fields, buf = buf.split(b"\n", 1) + fields = int(fields) + while len(sparse) < fields * 2: + if b"\n" not in buf: + buf += tarfile.fileobj.read(BLOCKSIZE) + number, buf = buf.split(b"\n", 1) + sparse.append(int(number)) + next.offset_data = tarfile.fileobj.tell() + next.sparse = list(zip(sparse[::2], sparse[1::2])) + + def _apply_pax_info(self, pax_headers, encoding, errors): + """Replace fields with supplemental information from a previous + pax extended or global header. + """ + for keyword, value in pax_headers.items(): + if keyword == "GNU.sparse.name": + setattr(self, "path", value) + elif keyword == "GNU.sparse.size": + setattr(self, "size", int(value)) + elif keyword == "GNU.sparse.realsize": + setattr(self, "size", int(value)) + elif keyword in PAX_FIELDS: + if keyword in PAX_NUMBER_FIELDS: + try: + value = PAX_NUMBER_FIELDS[keyword](value) + except ValueError: + value = 0 + if keyword == "path": + value = value.rstrip("/") + setattr(self, keyword, value) + + self.pax_headers = pax_headers.copy() + + def _decode_pax_field(self, value, encoding, fallback_encoding, fallback_errors): + """Decode a single field from a pax record. + """ + try: + return value.decode(encoding, "strict") + except UnicodeDecodeError: + return value.decode(fallback_encoding, fallback_errors) + + def _block(self, count): + """Round up a byte count by BLOCKSIZE and return it, + e.g. _block(834) => 1024. + """ + blocks, remainder = divmod(count, BLOCKSIZE) + if remainder: + blocks += 1 + return blocks * BLOCKSIZE + + def isreg(self): + 'Return True if the Tarinfo object is a regular file.' + return self.type in REGULAR_TYPES + + def isfile(self): + 'Return True if the Tarinfo object is a regular file.' + return self.isreg() + + def isdir(self): + 'Return True if it is a directory.' + return self.type == DIRTYPE + + def issym(self): + 'Return True if it is a symbolic link.' + return self.type == SYMTYPE + + def islnk(self): + 'Return True if it is a hard link.' + return self.type == LNKTYPE + + def ischr(self): + 'Return True if it is a character device.' + return self.type == CHRTYPE + + def isblk(self): + 'Return True if it is a block device.' + return self.type == BLKTYPE + + def isfifo(self): + 'Return True if it is a FIFO.' + return self.type == FIFOTYPE + + def issparse(self): + return self.sparse is not None + + def isdev(self): + 'Return True if it is one of character device, block device or FIFO.' + return self.type in (CHRTYPE, BLKTYPE, FIFOTYPE) +# class TarInfo + +class TarFile(object): + """The TarFile Class provides an interface to tar archives. + """ + + debug = 0 # May be set from 0 (no msgs) to 3 (all msgs) + + dereference = False # If true, add content of linked file to the + # tar file, else the link. + + ignore_zeros = False # If true, skips empty or invalid blocks and + # continues processing. + + errorlevel = 1 # If 0, fatal errors only appear in debug + # messages (if debug >= 0). If > 0, errors + # are passed to the caller as exceptions. + + format = DEFAULT_FORMAT # The format to use when creating an archive. + + encoding = ENCODING # Encoding for 8-bit character strings. + + errors = None # Error handler for unicode conversion. + + tarinfo = TarInfo # The default TarInfo class to use. + + fileobject = ExFileObject # The file-object for extractfile(). + + extraction_filter = None # The default filter for extraction. + + def __init__(self, name=None, mode="r", fileobj=None, format=None, + tarinfo=None, dereference=None, ignore_zeros=None, encoding=None, + errors="surrogateescape", pax_headers=None, debug=None, + errorlevel=None, copybufsize=None): + """Open an (uncompressed) tar archive `name'. `mode' is either 'r' to + read from an existing archive, 'a' to append data to an existing + file or 'w' to create a new file overwriting an existing one. `mode' + defaults to 'r'. + If `fileobj' is given, it is used for reading or writing data. If it + can be determined, `mode' is overridden by `fileobj's mode. + `fileobj' is not closed, when TarFile is closed. + """ + modes = {"r": "rb", "a": "r+b", "w": "wb", "x": "xb"} + if mode not in modes: + raise ValueError("mode must be 'r', 'a', 'w' or 'x'") + self.mode = mode + self._mode = modes[mode] + + if not fileobj: + if self.mode == "a" and not os.path.exists(name): + # Create nonexistent files in append mode. + self.mode = "w" + self._mode = "wb" + fileobj = bltn_open(name, self._mode) + self._extfileobj = False + else: + if (name is None and hasattr(fileobj, "name") and + isinstance(fileobj.name, (str, bytes))): + name = fileobj.name + if hasattr(fileobj, "mode"): + self._mode = fileobj.mode + self._extfileobj = True + self.name = os.path.abspath(name) if name else None + self.fileobj = fileobj + + # Init attributes. + if format is not None: + self.format = format + if tarinfo is not None: + self.tarinfo = tarinfo + if dereference is not None: + self.dereference = dereference + if ignore_zeros is not None: + self.ignore_zeros = ignore_zeros + if encoding is not None: + self.encoding = encoding + self.errors = errors + + if pax_headers is not None and self.format == PAX_FORMAT: + self.pax_headers = pax_headers + else: + self.pax_headers = {} + + if debug is not None: + self.debug = debug + if errorlevel is not None: + self.errorlevel = errorlevel + + # Init datastructures. + self.copybufsize = copybufsize + self.closed = False + self.members = [] # list of members as TarInfo objects + self._loaded = False # flag if all members have been read + self.offset = self.fileobj.tell() + # current position in the archive file + self.inodes = {} # dictionary caching the inodes of + # archive members already added + + try: + if self.mode == "r": + self.firstmember = None + self.firstmember = self.next() + + if self.mode == "a": + # Move to the end of the archive, + # before the first empty block. + while True: + self.fileobj.seek(self.offset) + try: + tarinfo = self.tarinfo.fromtarfile(self) + self.members.append(tarinfo) + except EOFHeaderError: + self.fileobj.seek(self.offset) + break + except HeaderError as e: + raise ReadError(str(e)) from None + + if self.mode in ("a", "w", "x"): + self._loaded = True + + if self.pax_headers: + buf = self.tarinfo.create_pax_global_header(self.pax_headers.copy()) + self.fileobj.write(buf) + self.offset += len(buf) + except: + if not self._extfileobj: + self.fileobj.close() + self.closed = True + raise + + #-------------------------------------------------------------------------- + # Below are the classmethods which act as alternate constructors to the + # TarFile class. The open() method is the only one that is needed for + # public use; it is the "super"-constructor and is able to select an + # adequate "sub"-constructor for a particular compression using the mapping + # from OPEN_METH. + # + # This concept allows one to subclass TarFile without losing the comfort of + # the super-constructor. A sub-constructor is registered and made available + # by adding it to the mapping in OPEN_METH. + + @classmethod + def open(cls, name=None, mode="r", fileobj=None, bufsize=RECORDSIZE, **kwargs): + r"""Open a tar archive for reading, writing or appending. Return + an appropriate TarFile class. + + mode: + 'r' or 'r:\*' open for reading with transparent compression + 'r:' open for reading exclusively uncompressed + 'r:gz' open for reading with gzip compression + 'r:bz2' open for reading with bzip2 compression + 'r:xz' open for reading with lzma compression + 'a' or 'a:' open for appending, creating the file if necessary + 'w' or 'w:' open for writing without compression + 'w:gz' open for writing with gzip compression + 'w:bz2' open for writing with bzip2 compression + 'w:xz' open for writing with lzma compression + + 'x' or 'x:' create a tarfile exclusively without compression, raise + an exception if the file is already created + 'x:gz' create a gzip compressed tarfile, raise an exception + if the file is already created + 'x:bz2' create a bzip2 compressed tarfile, raise an exception + if the file is already created + 'x:xz' create an lzma compressed tarfile, raise an exception + if the file is already created + + 'r|\*' open a stream of tar blocks with transparent compression + 'r|' open an uncompressed stream of tar blocks for reading + 'r|gz' open a gzip compressed stream of tar blocks + 'r|bz2' open a bzip2 compressed stream of tar blocks + 'r|xz' open an lzma compressed stream of tar blocks + 'w|' open an uncompressed stream for writing + 'w|gz' open a gzip compressed stream for writing + 'w|bz2' open a bzip2 compressed stream for writing + 'w|xz' open an lzma compressed stream for writing + """ + + if not name and not fileobj: + raise ValueError("nothing to open") + + if mode in ("r", "r:*"): + # Find out which *open() is appropriate for opening the file. + def not_compressed(comptype): + return cls.OPEN_METH[comptype] == 'taropen' + error_msgs = [] + for comptype in sorted(cls.OPEN_METH, key=not_compressed): + func = getattr(cls, cls.OPEN_METH[comptype]) + if fileobj is not None: + saved_pos = fileobj.tell() + try: + return func(name, "r", fileobj, **kwargs) + except (ReadError, CompressionError) as e: + error_msgs.append(f'- method {comptype}: {e!r}') + if fileobj is not None: + fileobj.seek(saved_pos) + continue + error_msgs_summary = '\n'.join(error_msgs) + raise ReadError(f"file could not be opened successfully:\n{error_msgs_summary}") + + elif ":" in mode: + filemode, comptype = mode.split(":", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + # Select the *open() function according to + # given compression. + if comptype in cls.OPEN_METH: + func = getattr(cls, cls.OPEN_METH[comptype]) + else: + raise CompressionError("unknown compression type %r" % comptype) + return func(name, filemode, fileobj, **kwargs) + + elif "|" in mode: + filemode, comptype = mode.split("|", 1) + filemode = filemode or "r" + comptype = comptype or "tar" + + if filemode not in ("r", "w"): + raise ValueError("mode must be 'r' or 'w'") + + compresslevel = kwargs.pop("compresslevel", 9) + stream = _Stream(name, filemode, comptype, fileobj, bufsize, + compresslevel) + try: + t = cls(name, filemode, stream, **kwargs) + except: + stream.close() + raise + t._extfileobj = False + return t + + elif mode in ("a", "w", "x"): + return cls.taropen(name, mode, fileobj, **kwargs) + + raise ValueError("undiscernible mode") + + @classmethod + def taropen(cls, name, mode="r", fileobj=None, **kwargs): + """Open uncompressed tar archive name for reading or writing. + """ + if mode not in ("r", "a", "w", "x"): + raise ValueError("mode must be 'r', 'a', 'w' or 'x'") + return cls(name, mode, fileobj, **kwargs) + + @classmethod + def gzopen(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs): + """Open gzip compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if mode not in ("r", "w", "x"): + raise ValueError("mode must be 'r', 'w' or 'x'") + + try: + from gzip import GzipFile + except ImportError: + raise CompressionError("gzip module is not available") from None + + try: + fileobj = GzipFile(name, mode + "b", compresslevel, fileobj) + except OSError as e: + if fileobj is not None and mode == 'r': + raise ReadError("not a gzip file") from e + raise + + try: + t = cls.taropen(name, mode, fileobj, **kwargs) + except OSError as e: + fileobj.close() + if mode == 'r': + raise ReadError("not a gzip file") from e + raise + except: + fileobj.close() + raise + t._extfileobj = False + return t + + @classmethod + def bz2open(cls, name, mode="r", fileobj=None, compresslevel=9, **kwargs): + """Open bzip2 compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if mode not in ("r", "w", "x"): + raise ValueError("mode must be 'r', 'w' or 'x'") + + try: + from bz2 import BZ2File + except ImportError: + raise CompressionError("bz2 module is not available") from None + + fileobj = BZ2File(fileobj or name, mode, compresslevel=compresslevel) + + try: + t = cls.taropen(name, mode, fileobj, **kwargs) + except (OSError, EOFError) as e: + fileobj.close() + if mode == 'r': + raise ReadError("not a bzip2 file") from e + raise + except: + fileobj.close() + raise + t._extfileobj = False + return t + + @classmethod + def xzopen(cls, name, mode="r", fileobj=None, preset=None, **kwargs): + """Open lzma compressed tar archive name for reading or writing. + Appending is not allowed. + """ + if mode not in ("r", "w", "x"): + raise ValueError("mode must be 'r', 'w' or 'x'") + + try: + from lzma import LZMAFile, LZMAError + except ImportError: + raise CompressionError("lzma module is not available") from None + + fileobj = LZMAFile(fileobj or name, mode, preset=preset) + + try: + t = cls.taropen(name, mode, fileobj, **kwargs) + except (LZMAError, EOFError) as e: + fileobj.close() + if mode == 'r': + raise ReadError("not an lzma file") from e + raise + except: + fileobj.close() + raise + t._extfileobj = False + return t + + # All *open() methods are registered here. + OPEN_METH = { + "tar": "taropen", # uncompressed tar + "gz": "gzopen", # gzip compressed tar + "bz2": "bz2open", # bzip2 compressed tar + "xz": "xzopen" # lzma compressed tar + } + + #-------------------------------------------------------------------------- + # The public methods which TarFile provides: + + def close(self): + """Close the TarFile. In write-mode, two finishing zero blocks are + appended to the archive. + """ + if self.closed: + return + + self.closed = True + try: + if self.mode in ("a", "w", "x"): + self.fileobj.write(NUL * (BLOCKSIZE * 2)) + self.offset += (BLOCKSIZE * 2) + # fill up the end with zero-blocks + # (like option -b20 for tar does) + blocks, remainder = divmod(self.offset, RECORDSIZE) + if remainder > 0: + self.fileobj.write(NUL * (RECORDSIZE - remainder)) + finally: + if not self._extfileobj: + self.fileobj.close() + + def getmember(self, name): + """Return a TarInfo object for member ``name``. If ``name`` can not be + found in the archive, KeyError is raised. If a member occurs more + than once in the archive, its last occurrence is assumed to be the + most up-to-date version. + """ + tarinfo = self._getmember(name.rstrip('/')) + if tarinfo is None: + raise KeyError("filename %r not found" % name) + return tarinfo + + def getmembers(self): + """Return the members of the archive as a list of TarInfo objects. The + list has the same order as the members in the archive. + """ + self._check() + if not self._loaded: # if we want to obtain a list of + self._load() # all members, we first have to + # scan the whole archive. + return self.members + + def getnames(self): + """Return the members of the archive as a list of their names. It has + the same order as the list returned by getmembers(). + """ + return [tarinfo.name for tarinfo in self.getmembers()] + + def gettarinfo(self, name=None, arcname=None, fileobj=None): + """Create a TarInfo object from the result of os.stat or equivalent + on an existing file. The file is either named by ``name``, or + specified as a file object ``fileobj`` with a file descriptor. If + given, ``arcname`` specifies an alternative name for the file in the + archive, otherwise, the name is taken from the 'name' attribute of + 'fileobj', or the 'name' argument. The name should be a text + string. + """ + self._check("awx") + + # When fileobj is given, replace name by + # fileobj's real name. + if fileobj is not None: + name = fileobj.name + + # Building the name of the member in the archive. + # Backward slashes are converted to forward slashes, + # Absolute paths are turned to relative paths. + if arcname is None: + arcname = name + drv, arcname = os.path.splitdrive(arcname) + arcname = arcname.replace(os.sep, "/") + arcname = arcname.lstrip("/") + + # Now, fill the TarInfo object with + # information specific for the file. + tarinfo = self.tarinfo() + tarinfo.tarfile = self # Not needed + + # Use os.stat or os.lstat, depending on if symlinks shall be resolved. + if fileobj is None: + if not self.dereference: + statres = os.lstat(name) + else: + statres = os.stat(name) + else: + statres = os.fstat(fileobj.fileno()) + linkname = "" + + stmd = statres.st_mode + if stat.S_ISREG(stmd): + inode = (statres.st_ino, statres.st_dev) + if not self.dereference and statres.st_nlink > 1 and \ + inode in self.inodes and arcname != self.inodes[inode]: + # Is it a hardlink to an already + # archived file? + type = LNKTYPE + linkname = self.inodes[inode] + else: + # The inode is added only if its valid. + # For win32 it is always 0. + type = REGTYPE + if inode[0]: + self.inodes[inode] = arcname + elif stat.S_ISDIR(stmd): + type = DIRTYPE + elif stat.S_ISFIFO(stmd): + type = FIFOTYPE + elif stat.S_ISLNK(stmd): + type = SYMTYPE + linkname = os.readlink(name) + elif stat.S_ISCHR(stmd): + type = CHRTYPE + elif stat.S_ISBLK(stmd): + type = BLKTYPE + else: + return None + + # Fill the TarInfo object with all + # information we can get. + tarinfo.name = arcname + tarinfo.mode = stmd + tarinfo.uid = statres.st_uid + tarinfo.gid = statres.st_gid + if type == REGTYPE: + tarinfo.size = statres.st_size + else: + tarinfo.size = 0 + tarinfo.mtime = statres.st_mtime + tarinfo.type = type + tarinfo.linkname = linkname + if pwd: + try: + tarinfo.uname = pwd.getpwuid(tarinfo.uid)[0] + except KeyError: + pass + if grp: + try: + tarinfo.gname = grp.getgrgid(tarinfo.gid)[0] + except KeyError: + pass + + if type in (CHRTYPE, BLKTYPE): + if hasattr(os, "major") and hasattr(os, "minor"): + tarinfo.devmajor = os.major(statres.st_rdev) + tarinfo.devminor = os.minor(statres.st_rdev) + return tarinfo + + def list(self, verbose=True, *, members=None): + """Print a table of contents to sys.stdout. If ``verbose`` is False, only + the names of the members are printed. If it is True, an `ls -l'-like + output is produced. ``members`` is optional and must be a subset of the + list returned by getmembers(). + """ + self._check() + + if members is None: + members = self + for tarinfo in members: + if verbose: + if tarinfo.mode is None: + _safe_print("??????????") + else: + _safe_print(stat.filemode(tarinfo.mode)) + _safe_print("%s/%s" % (tarinfo.uname or tarinfo.uid, + tarinfo.gname or tarinfo.gid)) + if tarinfo.ischr() or tarinfo.isblk(): + _safe_print("%10s" % + ("%d,%d" % (tarinfo.devmajor, tarinfo.devminor))) + else: + _safe_print("%10d" % tarinfo.size) + if tarinfo.mtime is None: + _safe_print("????-??-?? ??:??:??") + else: + _safe_print("%d-%02d-%02d %02d:%02d:%02d" \ + % time.localtime(tarinfo.mtime)[:6]) + + _safe_print(tarinfo.name + ("/" if tarinfo.isdir() else "")) + + if verbose: + if tarinfo.issym(): + _safe_print("-> " + tarinfo.linkname) + if tarinfo.islnk(): + _safe_print("link to " + tarinfo.linkname) + print() + + def add(self, name, arcname=None, recursive=True, *, filter=None): + """Add the file ``name`` to the archive. ``name`` may be any type of file + (directory, fifo, symbolic link, etc.). If given, ``arcname`` + specifies an alternative name for the file in the archive. + Directories are added recursively by default. This can be avoided by + setting ``recursive`` to False. ``filter`` is a function + that expects a TarInfo object argument and returns the changed + TarInfo object, if it returns None the TarInfo object will be + excluded from the archive. + """ + self._check("awx") + + if arcname is None: + arcname = name + + # Skip if somebody tries to archive the archive... + if self.name is not None and os.path.abspath(name) == self.name: + self._dbg(2, "tarfile: Skipped %r" % name) + return + + self._dbg(1, name) + + # Create a TarInfo object from the file. + tarinfo = self.gettarinfo(name, arcname) + + if tarinfo is None: + self._dbg(1, "tarfile: Unsupported type %r" % name) + return + + # Change or exclude the TarInfo object. + if filter is not None: + tarinfo = filter(tarinfo) + if tarinfo is None: + self._dbg(2, "tarfile: Excluded %r" % name) + return + + # Append the tar header and data to the archive. + if tarinfo.isreg(): + with bltn_open(name, "rb") as f: + self.addfile(tarinfo, f) + + elif tarinfo.isdir(): + self.addfile(tarinfo) + if recursive: + for f in sorted(os.listdir(name)): + self.add(os.path.join(name, f), os.path.join(arcname, f), + recursive, filter=filter) + + else: + self.addfile(tarinfo) + + def addfile(self, tarinfo, fileobj=None): + """Add the TarInfo object ``tarinfo`` to the archive. If ``fileobj`` is + given, it should be a binary file, and tarinfo.size bytes are read + from it and added to the archive. You can create TarInfo objects + directly, or by using gettarinfo(). + """ + self._check("awx") + + tarinfo = copy.copy(tarinfo) + + buf = tarinfo.tobuf(self.format, self.encoding, self.errors) + self.fileobj.write(buf) + self.offset += len(buf) + bufsize=self.copybufsize + # If there's data to follow, append it. + if fileobj is not None: + copyfileobj(fileobj, self.fileobj, tarinfo.size, bufsize=bufsize) + blocks, remainder = divmod(tarinfo.size, BLOCKSIZE) + if remainder > 0: + self.fileobj.write(NUL * (BLOCKSIZE - remainder)) + blocks += 1 + self.offset += blocks * BLOCKSIZE + + self.members.append(tarinfo) + + def _get_filter_function(self, filter): + if filter is None: + filter = self.extraction_filter + if filter is None: + warnings.warn( + 'Python 3.14 will, by default, filter extracted tar ' + + 'archives and reject files or modify their metadata. ' + + 'Use the filter argument to control this behavior.', + DeprecationWarning) + return fully_trusted_filter + if isinstance(filter, str): + raise TypeError( + 'String names are not supported for ' + + 'TarFile.extraction_filter. Use a function such as ' + + 'tarfile.data_filter directly.') + return filter + if callable(filter): + return filter + try: + return _NAMED_FILTERS[filter] + except KeyError: + raise ValueError(f"filter {filter!r} not found") from None + + def extractall(self, path=".", members=None, *, numeric_owner=False, + filter=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). If `numeric_owner` is True, only + the numbers for user/group names are used and not the names. + + The `filter` function will be called on each member just + before extraction. + It can return a changed TarInfo or None to skip the member. + String names of common filters are accepted. + """ + directories = [] + + filter_function = self._get_filter_function(filter) + if members is None: + members = self + + for member in members: + tarinfo = self._get_extract_tarinfo(member, filter_function, path) + if tarinfo is None: + continue + if tarinfo.isdir(): + # For directories, delay setting attributes until later, + # since permissions can interfere with extraction and + # extracting contents can reset mtime. + directories.append(tarinfo) + self._extract_one(tarinfo, path, set_attrs=not tarinfo.isdir(), + numeric_owner=numeric_owner) + + # Reverse sort directories. + directories.sort(key=lambda a: a.name, reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath, numeric_owner=numeric_owner) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError as e: + self._handle_nonfatal_error(e) + + def extract(self, member, path="", set_attrs=True, *, numeric_owner=False, + filter=None): + """Extract a member from the archive to the current working directory, + using its full name. Its file information is extracted as accurately + as possible. `member' may be a filename or a TarInfo object. You can + specify a different directory using `path'. File attributes (owner, + mtime, mode) are set unless `set_attrs' is False. If `numeric_owner` + is True, only the numbers for user/group names are used and not + the names. + + The `filter` function will be called before extraction. + It can return a changed TarInfo or None to skip the member. + String names of common filters are accepted. + """ + filter_function = self._get_filter_function(filter) + tarinfo = self._get_extract_tarinfo(member, filter_function, path) + if tarinfo is not None: + self._extract_one(tarinfo, path, set_attrs, numeric_owner) + + def _get_extract_tarinfo(self, member, filter_function, path): + """Get filtered TarInfo (or None) from member, which might be a str""" + if isinstance(member, str): + tarinfo = self.getmember(member) + else: + tarinfo = member + + unfiltered = tarinfo + try: + tarinfo = filter_function(tarinfo, path) + except (OSError, FilterError) as e: + self._handle_fatal_error(e) + except ExtractError as e: + self._handle_nonfatal_error(e) + if tarinfo is None: + self._dbg(2, "tarfile: Excluded %r" % unfiltered.name) + return None + # Prepare the link target for makelink(). + if tarinfo.islnk(): + tarinfo = copy.copy(tarinfo) + tarinfo._link_target = os.path.join(path, tarinfo.linkname) + return tarinfo + + def _extract_one(self, tarinfo, path, set_attrs, numeric_owner): + """Extract from filtered tarinfo to disk""" + self._check("r") + + try: + self._extract_member(tarinfo, os.path.join(path, tarinfo.name), + set_attrs=set_attrs, + numeric_owner=numeric_owner) + except OSError as e: + self._handle_fatal_error(e) + except ExtractError as e: + self._handle_nonfatal_error(e) + + def _handle_nonfatal_error(self, e): + """Handle non-fatal error (ExtractError) according to errorlevel""" + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + def _handle_fatal_error(self, e): + """Handle "fatal" error according to self.errorlevel""" + if self.errorlevel > 0: + raise + elif isinstance(e, OSError): + if e.filename is None: + self._dbg(1, "tarfile: %s" % e.strerror) + else: + self._dbg(1, "tarfile: %s %r" % (e.strerror, e.filename)) + else: + self._dbg(1, "tarfile: %s %s" % (type(e).__name__, e)) + + def extractfile(self, member): + """Extract a member from the archive as a file object. ``member`` may be + a filename or a TarInfo object. If ``member`` is a regular file or + a link, an io.BufferedReader object is returned. For all other + existing members, None is returned. If ``member`` does not appear + in the archive, KeyError is raised. + """ + self._check("r") + + if isinstance(member, str): + tarinfo = self.getmember(member) + else: + tarinfo = member + + if tarinfo.isreg() or tarinfo.type not in SUPPORTED_TYPES: + # Members with unknown types are treated as regular files. + return self.fileobject(self, tarinfo) + + elif tarinfo.islnk() or tarinfo.issym(): + if isinstance(self.fileobj, _Stream): + # A small but ugly workaround for the case that someone tries + # to extract a (sym)link as a file-object from a non-seekable + # stream of tar blocks. + raise StreamError("cannot extract (sym)link as file object") + else: + # A (sym)link's file object is its target's file object. + return self.extractfile(self._find_link_target(tarinfo)) + else: + # If there's no data associated with the member (directory, chrdev, + # blkdev, etc.), return None instead of a file object. + return None + + def _extract_member(self, tarinfo, targetpath, set_attrs=True, + numeric_owner=False): + """Extract the TarInfo object tarinfo to a physical + file called targetpath. + """ + # Fetch the TarInfo object for the given name + # and build the destination pathname, replacing + # forward slashes to platform specific separators. + targetpath = targetpath.rstrip("/") + targetpath = targetpath.replace("/", os.sep) + + # Create all upper directories. + upperdirs = os.path.dirname(targetpath) + if upperdirs and not os.path.exists(upperdirs): + # Create directories that are not part of the archive with + # default permissions. + os.makedirs(upperdirs) + + if tarinfo.islnk() or tarinfo.issym(): + self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname)) + else: + self._dbg(1, tarinfo.name) + + if tarinfo.isreg(): + self.makefile(tarinfo, targetpath) + elif tarinfo.isdir(): + self.makedir(tarinfo, targetpath) + elif tarinfo.isfifo(): + self.makefifo(tarinfo, targetpath) + elif tarinfo.ischr() or tarinfo.isblk(): + self.makedev(tarinfo, targetpath) + elif tarinfo.islnk() or tarinfo.issym(): + self.makelink(tarinfo, targetpath) + elif tarinfo.type not in SUPPORTED_TYPES: + self.makeunknown(tarinfo, targetpath) + else: + self.makefile(tarinfo, targetpath) + + if set_attrs: + self.chown(tarinfo, targetpath, numeric_owner) + if not tarinfo.issym(): + self.chmod(tarinfo, targetpath) + self.utime(tarinfo, targetpath) + + #-------------------------------------------------------------------------- + # Below are the different file methods. They are called via + # _extract_member() when extract() is called. They can be replaced in a + # subclass to implement other functionality. + + def makedir(self, tarinfo, targetpath): + """Make a directory called targetpath. + """ + try: + if tarinfo.mode is None: + # Use the system's default mode + os.mkdir(targetpath) + else: + # Use a safe mode for the directory, the real mode is set + # later in _extract_member(). + os.mkdir(targetpath, 0o700) + except FileExistsError: + if not os.path.isdir(targetpath): + raise + + def makefile(self, tarinfo, targetpath): + """Make a file called targetpath. + """ + source = self.fileobj + source.seek(tarinfo.offset_data) + bufsize = self.copybufsize + with bltn_open(targetpath, "wb") as target: + if tarinfo.sparse is not None: + for offset, size in tarinfo.sparse: + target.seek(offset) + copyfileobj(source, target, size, ReadError, bufsize) + target.seek(tarinfo.size) + target.truncate() + else: + copyfileobj(source, target, tarinfo.size, ReadError, bufsize) + + def makeunknown(self, tarinfo, targetpath): + """Make a file from a TarInfo object with an unknown type + at targetpath. + """ + self.makefile(tarinfo, targetpath) + self._dbg(1, "tarfile: Unknown file type %r, " \ + "extracted as regular file." % tarinfo.type) + + def makefifo(self, tarinfo, targetpath): + """Make a fifo called targetpath. + """ + if hasattr(os, "mkfifo"): + os.mkfifo(targetpath) + else: + raise ExtractError("fifo not supported by system") + + def makedev(self, tarinfo, targetpath): + """Make a character or block device called targetpath. + """ + if not hasattr(os, "mknod") or not hasattr(os, "makedev"): + raise ExtractError("special devices not supported by system") + + mode = tarinfo.mode + if mode is None: + # Use mknod's default + mode = 0o600 + if tarinfo.isblk(): + mode |= stat.S_IFBLK + else: + mode |= stat.S_IFCHR + + os.mknod(targetpath, mode, + os.makedev(tarinfo.devmajor, tarinfo.devminor)) + + def makelink(self, tarinfo, targetpath): + """Make a (symbolic) link called targetpath. If it cannot be created + (platform limitation), we try to make a copy of the referenced file + instead of a link. + """ + try: + # For systems that support symbolic and hard links. + if tarinfo.issym(): + if os.path.lexists(targetpath): + # Avoid FileExistsError on following os.symlink. + os.unlink(targetpath) + os.symlink(tarinfo.linkname, targetpath) + else: + if os.path.exists(tarinfo._link_target): + os.link(tarinfo._link_target, targetpath) + else: + self._extract_member(self._find_link_target(tarinfo), + targetpath) + except symlink_exception: + try: + self._extract_member(self._find_link_target(tarinfo), + targetpath) + except KeyError: + raise ExtractError("unable to resolve link inside archive") from None + + def chown(self, tarinfo, targetpath, numeric_owner): + """Set owner of targetpath according to tarinfo. If numeric_owner + is True, use .gid/.uid instead of .gname/.uname. If numeric_owner + is False, fall back to .gid/.uid when the search based on name + fails. + """ + if hasattr(os, "geteuid") and os.geteuid() == 0: + # We have to be root to do so. + g = tarinfo.gid + u = tarinfo.uid + if not numeric_owner: + try: + if grp and tarinfo.gname: + g = grp.getgrnam(tarinfo.gname)[2] + except KeyError: + pass + try: + if pwd and tarinfo.uname: + u = pwd.getpwnam(tarinfo.uname)[2] + except KeyError: + pass + if g is None: + g = -1 + if u is None: + u = -1 + try: + if tarinfo.issym() and hasattr(os, "lchown"): + os.lchown(targetpath, u, g) + else: + os.chown(targetpath, u, g) + except OSError as e: + raise ExtractError("could not change owner") from e + + def chmod(self, tarinfo, targetpath): + """Set file permissions of targetpath according to tarinfo. + """ + if tarinfo.mode is None: + return + try: + os.chmod(targetpath, tarinfo.mode) + except OSError as e: + raise ExtractError("could not change mode") from e + + def utime(self, tarinfo, targetpath): + """Set modification time of targetpath according to tarinfo. + """ + mtime = tarinfo.mtime + if mtime is None: + return + if not hasattr(os, 'utime'): + return + try: + os.utime(targetpath, (mtime, mtime)) + except OSError as e: + raise ExtractError("could not change modification time") from e + + #-------------------------------------------------------------------------- + def next(self): + """Return the next member of the archive as a TarInfo object, when + TarFile is opened for reading. Return None if there is no more + available. + """ + self._check("ra") + if self.firstmember is not None: + m = self.firstmember + self.firstmember = None + return m + + # Advance the file pointer. + if self.offset != self.fileobj.tell(): + if self.offset == 0: + return None + self.fileobj.seek(self.offset - 1) + if not self.fileobj.read(1): + raise ReadError("unexpected end of data") + + # Read the next block. + tarinfo = None + while True: + try: + tarinfo = self.tarinfo.fromtarfile(self) + except EOFHeaderError as e: + if self.ignore_zeros: + self._dbg(2, "0x%X: %s" % (self.offset, e)) + self.offset += BLOCKSIZE + continue + except InvalidHeaderError as e: + if self.ignore_zeros: + self._dbg(2, "0x%X: %s" % (self.offset, e)) + self.offset += BLOCKSIZE + continue + elif self.offset == 0: + raise ReadError(str(e)) from None + except EmptyHeaderError: + if self.offset == 0: + raise ReadError("empty file") from None + except TruncatedHeaderError as e: + if self.offset == 0: + raise ReadError(str(e)) from None + except SubsequentHeaderError as e: + raise ReadError(str(e)) from None + except Exception as e: + try: + import zlib + if isinstance(e, zlib.error): + raise ReadError(f'zlib error: {e}') from None + else: + raise e + except ImportError: + raise e + break + + if tarinfo is not None: + self.members.append(tarinfo) + else: + self._loaded = True + + return tarinfo + + #-------------------------------------------------------------------------- + # Little helper methods: + + def _getmember(self, name, tarinfo=None, normalize=False): + """Find an archive member by name from bottom to top. + If tarinfo is given, it is used as the starting point. + """ + # Ensure that all members have been loaded. + members = self.getmembers() + + # Limit the member search list up to tarinfo. + skipping = False + if tarinfo is not None: + try: + index = members.index(tarinfo) + except ValueError: + # The given starting point might be a (modified) copy. + # We'll later skip members until we find an equivalent. + skipping = True + else: + # Happy fast path + members = members[:index] + + if normalize: + name = os.path.normpath(name) + + for member in reversed(members): + if skipping: + if tarinfo.offset == member.offset: + skipping = False + continue + if normalize: + member_name = os.path.normpath(member.name) + else: + member_name = member.name + + if name == member_name: + return member + + if skipping: + # Starting point was not found + raise ValueError(tarinfo) + + def _load(self): + """Read through the entire archive file and look for readable + members. + """ + while self.next() is not None: + pass + self._loaded = True + + def _check(self, mode=None): + """Check if TarFile is still open, and if the operation's mode + corresponds to TarFile's mode. + """ + if self.closed: + raise OSError("%s is closed" % self.__class__.__name__) + if mode is not None and self.mode not in mode: + raise OSError("bad operation for mode %r" % self.mode) + + def _find_link_target(self, tarinfo): + """Find the target member of a symlink or hardlink member in the + archive. + """ + if tarinfo.issym(): + # Always search the entire archive. + linkname = "/".join(filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname))) + limit = None + else: + # Search the archive before the link, because a hard link is + # just a reference to an already archived file. + linkname = tarinfo.linkname + limit = tarinfo + + member = self._getmember(linkname, tarinfo=limit, normalize=True) + if member is None: + raise KeyError("linkname %r not found" % linkname) + return member + + def __iter__(self): + """Provide an iterator object. + """ + if self._loaded: + yield from self.members + return + + # Yield items using TarFile's next() method. + # When all members have been read, set TarFile as _loaded. + index = 0 + # Fix for SF #1100429: Under rare circumstances it can + # happen that getmembers() is called during iteration, + # which will have already exhausted the next() method. + if self.firstmember is not None: + tarinfo = self.next() + index += 1 + yield tarinfo + + while True: + if index < len(self.members): + tarinfo = self.members[index] + elif not self._loaded: + tarinfo = self.next() + if not tarinfo: + self._loaded = True + return + else: + return + index += 1 + yield tarinfo + + def _dbg(self, level, msg): + """Write debugging output to sys.stderr. + """ + if level <= self.debug: + print(msg, file=sys.stderr) + + def __enter__(self): + self._check() + return self + + def __exit__(self, type, value, traceback): + if type is None: + self.close() + else: + # An exception occurred. We must not call close() because + # it would try to write end-of-archive blocks and padding. + if not self._extfileobj: + self.fileobj.close() + self.closed = True + +#-------------------- +# exported functions +#-------------------- + +def is_tarfile(name): + """Return True if name points to a tar archive that we + are able to handle, else return False. + + 'name' should be a string, file, or file-like object. + """ + try: + if hasattr(name, "read"): + pos = name.tell() + t = open(fileobj=name) + name.seek(pos) + else: + t = open(name) + t.close() + return True + except TarError: + return False + +open = TarFile.open + + +def main(): + import argparse + + description = 'A simple command-line interface for tarfile module.' + parser = argparse.ArgumentParser(description=description) + parser.add_argument('-v', '--verbose', action='store_true', default=False, + help='Verbose output') + parser.add_argument('--filter', metavar='', + choices=_NAMED_FILTERS, + help='Filter for extraction') + + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-l', '--list', metavar='', + help='Show listing of a tarfile') + group.add_argument('-e', '--extract', nargs='+', + metavar=('', ''), + help='Extract tarfile into target dir') + group.add_argument('-c', '--create', nargs='+', + metavar=('', ''), + help='Create tarfile from sources') + group.add_argument('-t', '--test', metavar='', + help='Test if a tarfile is valid') + + args = parser.parse_args() + + if args.filter and args.extract is None: + parser.exit(1, '--filter is only valid for extraction\n') + + if args.test is not None: + src = args.test + if is_tarfile(src): + with open(src, 'r') as tar: + tar.getmembers() + print(tar.getmembers(), file=sys.stderr) + if args.verbose: + print('{!r} is a tar archive.'.format(src)) + else: + parser.exit(1, '{!r} is not a tar archive.\n'.format(src)) + + elif args.list is not None: + src = args.list + if is_tarfile(src): + with TarFile.open(src, 'r:*') as tf: + tf.list(verbose=args.verbose) + else: + parser.exit(1, '{!r} is not a tar archive.\n'.format(src)) + + elif args.extract is not None: + if len(args.extract) == 1: + src = args.extract[0] + curdir = os.curdir + elif len(args.extract) == 2: + src, curdir = args.extract + else: + parser.exit(1, parser.format_help()) + + if is_tarfile(src): + with TarFile.open(src, 'r:*') as tf: + tf.extractall(path=curdir, filter=args.filter) + if args.verbose: + if curdir == '.': + msg = '{!r} file is extracted.'.format(src) + else: + msg = ('{!r} file is extracted ' + 'into {!r} directory.').format(src, curdir) + print(msg) + else: + parser.exit(1, '{!r} is not a tar archive.\n'.format(src)) + + elif args.create is not None: + tar_name = args.create.pop(0) + _, ext = os.path.splitext(tar_name) + compressions = { + # gz + '.gz': 'gz', + '.tgz': 'gz', + # xz + '.xz': 'xz', + '.txz': 'xz', + # bz2 + '.bz2': 'bz2', + '.tbz': 'bz2', + '.tbz2': 'bz2', + '.tb2': 'bz2', + } + tar_mode = 'w:' + compressions[ext] if ext in compressions else 'w' + tar_files = args.create + + with TarFile.open(tar_name, tar_mode) as tf: + for file_name in tar_files: + tf.add(file_name) + + if args.verbose: + print('{!r} file created.'.format(tar_name)) + +if __name__ == '__main__': + main() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..34e3a9950cc557879af8d797f9382b18a870fb56 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py @@ -0,0 +1,36 @@ +"""Read resources contained within a package.""" + +from ._common import ( + as_file, + files, + Package, +) + +from ._legacy import ( + contents, + open_binary, + read_binary, + open_text, + read_text, + is_resource, + path, + Resource, +) + +from .abc import ResourceReader + + +__all__ = [ + 'Package', + 'Resource', + 'ResourceReader', + 'as_file', + 'contents', + 'files', + 'is_resource', + 'open_binary', + 'open_text', + 'path', + 'read_binary', + 'read_text', +] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f95862e0e32fddc614aeee73a3ee2345263f17c4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_adapters.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31b86b1c3f6969e4080d9ace22ab2e6a63337cd3 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_adapters.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_common.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_common.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24035214e1ed45c065839dc01ca7e511485cfb4d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_common.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_compat.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b819977bc0b752afc241285d993f272808b5be33 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_compat.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_itertools.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_itertools.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fd11e9cd156dcd3b69d0180634a42ecfc53d689 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_itertools.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_legacy.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_legacy.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe23914b5c21e424ba1578b1de88a0f9b9b78fed Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/_legacy.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/abc.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/abc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cb81b0c84c9f74d5c588006a932fb332f0da51b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/abc.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/readers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/readers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23a4abe0a9b3b6afd55a0cdab5d40135d531ce19 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/readers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/simple.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/simple.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a48ba5a334475bc4df1b46f22f725bb50e8d1a2e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/__pycache__/simple.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py new file mode 100644 index 0000000000000000000000000000000000000000..ea363d86a564b5450666aa00aecd46353326a75a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py @@ -0,0 +1,170 @@ +from contextlib import suppress +from io import TextIOWrapper + +from . import abc + + +class SpecLoaderAdapter: + """ + Adapt a package spec to adapt the underlying loader. + """ + + def __init__(self, spec, adapter=lambda spec: spec.loader): + self.spec = spec + self.loader = adapter(spec) + + def __getattr__(self, name): + return getattr(self.spec, name) + + +class TraversableResourcesLoader: + """ + Adapt a loader to provide TraversableResources. + """ + + def __init__(self, spec): + self.spec = spec + + def get_resource_reader(self, name): + return CompatibilityFiles(self.spec)._native() + + +def _io_wrapper(file, mode='r', *args, **kwargs): + if mode == 'r': + return TextIOWrapper(file, *args, **kwargs) + elif mode == 'rb': + return file + raise ValueError( + "Invalid mode value '{}', only 'r' and 'rb' are supported".format(mode) + ) + + +class CompatibilityFiles: + """ + Adapter for an existing or non-existent resource reader + to provide a compatibility .files(). + """ + + class SpecPath(abc.Traversable): + """ + Path tied to a module spec. + Can be read and exposes the resource reader children. + """ + + def __init__(self, spec, reader): + self._spec = spec + self._reader = reader + + def iterdir(self): + if not self._reader: + return iter(()) + return iter( + CompatibilityFiles.ChildPath(self._reader, path) + for path in self._reader.contents() + ) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + if not self._reader: + return CompatibilityFiles.OrphanPath(other) + return CompatibilityFiles.ChildPath(self._reader, other) + + @property + def name(self): + return self._spec.name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper(self._reader.open_resource(None), mode, *args, **kwargs) + + class ChildPath(abc.Traversable): + """ + Path tied to a resource reader child. + Can be read but doesn't expose any meaningful children. + """ + + def __init__(self, reader, name): + self._reader = reader + self._name = name + + def iterdir(self): + return iter(()) + + def is_file(self): + return self._reader.is_resource(self.name) + + def is_dir(self): + return not self.is_file() + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(self.name, other) + + @property + def name(self): + return self._name + + def open(self, mode='r', *args, **kwargs): + return _io_wrapper( + self._reader.open_resource(self.name), mode, *args, **kwargs + ) + + class OrphanPath(abc.Traversable): + """ + Orphan path, not tied to a module spec or resource reader. + Can't be read and doesn't expose any meaningful children. + """ + + def __init__(self, *path_parts): + if len(path_parts) < 1: + raise ValueError('Need at least one path part to construct a path') + self._path = path_parts + + def iterdir(self): + return iter(()) + + def is_file(self): + return False + + is_dir = is_file + + def joinpath(self, other): + return CompatibilityFiles.OrphanPath(*self._path, other) + + @property + def name(self): + return self._path[-1] + + def open(self, mode='r', *args, **kwargs): + raise FileNotFoundError("Can't open orphan path") + + def __init__(self, spec): + self.spec = spec + + @property + def _reader(self): + with suppress(AttributeError): + return self.spec.loader.get_resource_reader(self.spec.name) + + def _native(self): + """ + Return the native reader if it supports files(). + """ + reader = self._reader + return reader if hasattr(reader, 'files') else self + + def __getattr__(self, attr): + return getattr(self._reader, attr) + + def files(self): + return CompatibilityFiles.SpecPath(self.spec, self._reader) + + +def wrap_spec(package): + """ + Construct a package spec with traversable compatibility + on the spec/loader/reader. + """ + return SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_common.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_common.py new file mode 100644 index 0000000000000000000000000000000000000000..3c6de1cfb2e7b8f4ae95100589c4eaa84fb99926 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_common.py @@ -0,0 +1,207 @@ +import os +import pathlib +import tempfile +import functools +import contextlib +import types +import importlib +import inspect +import warnings +import itertools + +from typing import Union, Optional, cast +from .abc import ResourceReader, Traversable + +from ._compat import wrap_spec + +Package = Union[types.ModuleType, str] +Anchor = Package + + +def package_to_anchor(func): + """ + Replace 'package' parameter as 'anchor' and warn about the change. + + Other errors should fall through. + + >>> files('a', 'b') + Traceback (most recent call last): + TypeError: files() takes from 0 to 1 positional arguments but 2 were given + """ + undefined = object() + + @functools.wraps(func) + def wrapper(anchor=undefined, package=undefined): + if package is not undefined: + if anchor is not undefined: + return func(anchor, package) + warnings.warn( + "First parameter to files is renamed to 'anchor'", + DeprecationWarning, + stacklevel=2, + ) + return func(package) + elif anchor is undefined: + return func() + return func(anchor) + + return wrapper + + +@package_to_anchor +def files(anchor: Optional[Anchor] = None) -> Traversable: + """ + Get a Traversable resource for an anchor. + """ + return from_package(resolve(anchor)) + + +def get_resource_reader(package: types.ModuleType) -> Optional[ResourceReader]: + """ + Return the package's loader if it's a ResourceReader. + """ + # We can't use + # a issubclass() check here because apparently abc.'s __subclasscheck__() + # hook wants to create a weak reference to the object, but + # zipimport.zipimporter does not support weak references, resulting in a + # TypeError. That seems terrible. + spec = package.__spec__ + reader = getattr(spec.loader, 'get_resource_reader', None) # type: ignore + if reader is None: + return None + return reader(spec.name) # type: ignore + + +@functools.singledispatch +def resolve(cand: Optional[Anchor]) -> types.ModuleType: + return cast(types.ModuleType, cand) + + +@resolve.register +def _(cand: str) -> types.ModuleType: + return importlib.import_module(cand) + + +@resolve.register +def _(cand: None) -> types.ModuleType: + return resolve(_infer_caller().f_globals['__name__']) + + +def _infer_caller(): + """ + Walk the stack and find the frame of the first caller not in this module. + """ + + def is_this_file(frame_info): + return frame_info.filename == __file__ + + def is_wrapper(frame_info): + return frame_info.function == 'wrapper' + + not_this_file = itertools.filterfalse(is_this_file, inspect.stack()) + # also exclude 'wrapper' due to singledispatch in the call stack + callers = itertools.filterfalse(is_wrapper, not_this_file) + return next(callers).frame + + +def from_package(package: types.ModuleType): + """ + Return a Traversable object for the given package. + + """ + spec = wrap_spec(package) + reader = spec.loader.get_resource_reader(spec.name) + return reader.files() + + +@contextlib.contextmanager +def _tempfile( + reader, + suffix='', + # gh-93353: Keep a reference to call os.remove() in late Python + # finalization. + *, + _os_remove=os.remove, +): + # Not using tempfile.NamedTemporaryFile as it leads to deeper 'try' + # blocks due to the need to close the temporary file to work on Windows + # properly. + fd, raw_path = tempfile.mkstemp(suffix=suffix) + try: + try: + os.write(fd, reader()) + finally: + os.close(fd) + del reader + yield pathlib.Path(raw_path) + finally: + try: + _os_remove(raw_path) + except FileNotFoundError: + pass + + +def _temp_file(path): + return _tempfile(path.read_bytes, suffix=path.name) + + +def _is_present_dir(path: Traversable) -> bool: + """ + Some Traversables implement ``is_dir()`` to raise an + exception (i.e. ``FileNotFoundError``) when the + directory doesn't exist. This function wraps that call + to always return a boolean and only return True + if there's a dir and it exists. + """ + with contextlib.suppress(FileNotFoundError): + return path.is_dir() + return False + + +@functools.singledispatch +def as_file(path): + """ + Given a Traversable object, return that object as a + path on the local file system in a context manager. + """ + return _temp_dir(path) if _is_present_dir(path) else _temp_file(path) + + +@as_file.register(pathlib.Path) +@contextlib.contextmanager +def _(path): + """ + Degenerate behavior for pathlib.Path objects. + """ + yield path + + +@contextlib.contextmanager +def _temp_path(dir: tempfile.TemporaryDirectory): + """ + Wrap tempfile.TemporyDirectory to return a pathlib object. + """ + with dir as result: + yield pathlib.Path(result) + + +@contextlib.contextmanager +def _temp_dir(path): + """ + Given a traversable dir, recursively replicate the whole tree + to the file system in a context manager. + """ + assert path.is_dir() + with _temp_path(tempfile.TemporaryDirectory()) as temp_dir: + yield _write_contents(temp_dir, path) + + +def _write_contents(target, source): + child = target.joinpath(source.name) + if source.is_dir(): + child.mkdir() + for item in source.iterdir(): + _write_contents(child, item) + else: + child.write_bytes(source.read_bytes()) + return child diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..8b5b1d280f3c7b45cee54338f60d5271a7510c2e --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py @@ -0,0 +1,108 @@ +# flake8: noqa + +import abc +import os +import sys +import pathlib +from contextlib import suppress +from typing import Union + + +if sys.version_info >= (3, 10): + from zipfile import Path as ZipPath # type: ignore +else: + from ..zipp import Path as ZipPath # type: ignore + + +try: + from typing import runtime_checkable # type: ignore +except ImportError: + + def runtime_checkable(cls): # type: ignore + return cls + + +try: + from typing import Protocol # type: ignore +except ImportError: + Protocol = abc.ABC # type: ignore + + +class TraversableResourcesLoader: + """ + Adapt loaders to provide TraversableResources and other + compatibility. + + Used primarily for Python 3.9 and earlier where the native + loaders do not yet implement TraversableResources. + """ + + def __init__(self, spec): + self.spec = spec + + @property + def path(self): + return self.spec.origin + + def get_resource_reader(self, name): + from . import readers, _adapters + + def _zip_reader(spec): + with suppress(AttributeError): + return readers.ZipReader(spec.loader, spec.name) + + def _namespace_reader(spec): + with suppress(AttributeError, ValueError): + return readers.NamespaceReader(spec.submodule_search_locations) + + def _available_reader(spec): + with suppress(AttributeError): + return spec.loader.get_resource_reader(spec.name) + + def _native_reader(spec): + reader = _available_reader(spec) + return reader if hasattr(reader, 'files') else None + + def _file_reader(spec): + try: + path = pathlib.Path(self.path) + except TypeError: + return None + if path.exists(): + return readers.FileReader(self) + + return ( + # native reader if it supplies 'files' + _native_reader(self.spec) + or + # local ZipReader if a zip module + _zip_reader(self.spec) + or + # local NamespaceReader if a namespace module + _namespace_reader(self.spec) + or + # local FileReader + _file_reader(self.spec) + # fallback - adapt the spec ResourceReader to TraversableReader + or _adapters.CompatibilityFiles(self.spec) + ) + + +def wrap_spec(package): + """ + Construct a package spec with traversable compatibility + on the spec/loader/reader. + + Supersedes _adapters.wrap_spec to use TraversableResourcesLoader + from above for older Python compatibility (<3.10). + """ + from . import _adapters + + return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader) + + +if sys.version_info >= (3, 9): + StrPath = Union[str, os.PathLike[str]] +else: + # PathLike is only subscriptable at runtime in 3.9+ + StrPath = Union[str, "os.PathLike[str]"] diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py new file mode 100644 index 0000000000000000000000000000000000000000..cce05582ffc6fe6d72027194f4ccc44ee42f1fcd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py @@ -0,0 +1,35 @@ +from itertools import filterfalse + +from typing import ( + Callable, + Iterable, + Iterator, + Optional, + Set, + TypeVar, + Union, +) + +# Type and type variable definitions +_T = TypeVar('_T') +_U = TypeVar('_U') + + +def unique_everseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = None +) -> Iterator[_T]: + "List unique elements, preserving order. Remember all elements ever seen." + # unique_everseen('AAAABBBCCDAABBB') --> A B C D + # unique_everseen('ABBCcAD', str.lower) --> A B C D + seen: Set[Union[_T, _U]] = set() + seen_add = seen.add + if key is None: + for element in filterfalse(seen.__contains__, iterable): + seen_add(element) + yield element + else: + for element in iterable: + k = key(element) + if k not in seen: + seen_add(k) + yield element diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py new file mode 100644 index 0000000000000000000000000000000000000000..b1ea8105dad6e27eefd5a34f64dfee974a5c4f71 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py @@ -0,0 +1,120 @@ +import functools +import os +import pathlib +import types +import warnings + +from typing import Union, Iterable, ContextManager, BinaryIO, TextIO, Any + +from . import _common + +Package = Union[types.ModuleType, str] +Resource = str + + +def deprecated(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + warnings.warn( + f"{func.__name__} is deprecated. Use files() instead. " + "Refer to https://importlib-resources.readthedocs.io" + "/en/latest/using.html#migrating-from-legacy for migration advice.", + DeprecationWarning, + stacklevel=2, + ) + return func(*args, **kwargs) + + return wrapper + + +def normalize_path(path: Any) -> str: + """Normalize a path by ensuring it is a string. + + If the resulting string contains path separators, an exception is raised. + """ + str_path = str(path) + parent, file_name = os.path.split(str_path) + if parent: + raise ValueError(f'{path!r} must be only a file name') + return file_name + + +@deprecated +def open_binary(package: Package, resource: Resource) -> BinaryIO: + """Return a file-like object opened for binary reading of the resource.""" + return (_common.files(package) / normalize_path(resource)).open('rb') + + +@deprecated +def read_binary(package: Package, resource: Resource) -> bytes: + """Return the binary contents of the resource.""" + return (_common.files(package) / normalize_path(resource)).read_bytes() + + +@deprecated +def open_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict', +) -> TextIO: + """Return a file-like object opened for text reading of the resource.""" + return (_common.files(package) / normalize_path(resource)).open( + 'r', encoding=encoding, errors=errors + ) + + +@deprecated +def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict', +) -> str: + """Return the decoded string of the resource. + + The decoding-related arguments have the same semantics as those of + bytes.decode(). + """ + with open_text(package, resource, encoding, errors) as fp: + return fp.read() + + +@deprecated +def contents(package: Package) -> Iterable[str]: + """Return an iterable of entries in `package`. + + Note that not all entries are resources. Specifically, directories are + not considered resources. Use `is_resource()` on each entry returned here + to check if it is a resource or not. + """ + return [path.name for path in _common.files(package).iterdir()] + + +@deprecated +def is_resource(package: Package, name: str) -> bool: + """True if `name` is a resource inside `package`. + + Directories are *not* resources. + """ + resource = normalize_path(name) + return any( + traversable.name == resource and traversable.is_file() + for traversable in _common.files(package).iterdir() + ) + + +@deprecated +def path( + package: Package, + resource: Resource, +) -> ContextManager[pathlib.Path]: + """A context manager providing a file path object to the resource. + + If the resource does not already exist on its own on the file system, + a temporary file will be created. If the file was created, the file + will be deleted upon exiting the context manager (no exception is + raised if the file was deleted prior to the context manager + exiting). + """ + return _common.as_file(_common.files(package) / normalize_path(resource)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/abc.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/abc.py new file mode 100644 index 0000000000000000000000000000000000000000..23b6aeafe4f43d097734e186907232513ad27a3c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/abc.py @@ -0,0 +1,170 @@ +import abc +import io +import itertools +import pathlib +from typing import Any, BinaryIO, Iterable, Iterator, NoReturn, Text, Optional + +from ._compat import runtime_checkable, Protocol, StrPath + + +__all__ = ["ResourceReader", "Traversable", "TraversableResources"] + + +class ResourceReader(metaclass=abc.ABCMeta): + """Abstract base class for loaders to provide resource reading support.""" + + @abc.abstractmethod + def open_resource(self, resource: Text) -> BinaryIO: + """Return an opened, file-like object for binary reading. + + The 'resource' argument is expected to represent only a file name. + If the resource cannot be found, FileNotFoundError is raised. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def resource_path(self, resource: Text) -> Text: + """Return the file system path to the specified resource. + + The 'resource' argument is expected to represent only a file name. + If the resource does not exist on the file system, raise + FileNotFoundError. + """ + # This deliberately raises FileNotFoundError instead of + # NotImplementedError so that if this method is accidentally called, + # it'll still do the right thing. + raise FileNotFoundError + + @abc.abstractmethod + def is_resource(self, path: Text) -> bool: + """Return True if the named 'path' is a resource. + + Files are resources, directories are not. + """ + raise FileNotFoundError + + @abc.abstractmethod + def contents(self) -> Iterable[str]: + """Return an iterable of entries in `package`.""" + raise FileNotFoundError + + +class TraversalError(Exception): + pass + + +@runtime_checkable +class Traversable(Protocol): + """ + An object with a subset of pathlib.Path methods suitable for + traversing directories and opening files. + + Any exceptions that occur when accessing the backing resource + may propagate unaltered. + """ + + @abc.abstractmethod + def iterdir(self) -> Iterator["Traversable"]: + """ + Yield Traversable objects in self + """ + + def read_bytes(self) -> bytes: + """ + Read contents of self as bytes + """ + with self.open('rb') as strm: + return strm.read() + + def read_text(self, encoding: Optional[str] = None) -> str: + """ + Read contents of self as text + """ + with self.open(encoding=encoding) as strm: + return strm.read() + + @abc.abstractmethod + def is_dir(self) -> bool: + """ + Return True if self is a directory + """ + + @abc.abstractmethod + def is_file(self) -> bool: + """ + Return True if self is a file + """ + + def joinpath(self, *descendants: StrPath) -> "Traversable": + """ + Return Traversable resolved with any descendants applied. + + Each descendant should be a path segment relative to self + and each may contain multiple levels separated by + ``posixpath.sep`` (``/``). + """ + if not descendants: + return self + names = itertools.chain.from_iterable( + path.parts for path in map(pathlib.PurePosixPath, descendants) + ) + target = next(names) + matches = ( + traversable for traversable in self.iterdir() if traversable.name == target + ) + try: + match = next(matches) + except StopIteration: + raise TraversalError( + "Target not found during traversal.", target, list(names) + ) + return match.joinpath(*names) + + def __truediv__(self, child: StrPath) -> "Traversable": + """ + Return Traversable child in self + """ + return self.joinpath(child) + + @abc.abstractmethod + def open(self, mode='r', *args, **kwargs): + """ + mode may be 'r' or 'rb' to open as text or binary. Return a handle + suitable for reading (same as pathlib.Path.open). + + When opening as text, accepts encoding parameters such as those + accepted by io.TextIOWrapper. + """ + + @property + @abc.abstractmethod + def name(self) -> str: + """ + The base name of this object without any parent references. + """ + + +class TraversableResources(ResourceReader): + """ + The required interface for providing traversable + resources. + """ + + @abc.abstractmethod + def files(self) -> "Traversable": + """Return a Traversable object for the loaded package.""" + + def open_resource(self, resource: StrPath) -> io.BufferedReader: + return self.files().joinpath(resource).open('rb') + + def resource_path(self, resource: Any) -> NoReturn: + raise FileNotFoundError(resource) + + def is_resource(self, path: StrPath) -> bool: + return self.files().joinpath(path).is_file() + + def contents(self) -> Iterator[str]: + return (item.name for item in self.files().iterdir()) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/py.typed b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/readers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/readers.py new file mode 100644 index 0000000000000000000000000000000000000000..ab34db74091c8a04ee9004ce9a786de3146ec917 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/readers.py @@ -0,0 +1,120 @@ +import collections +import pathlib +import operator + +from . import abc + +from ._itertools import unique_everseen +from ._compat import ZipPath + + +def remove_duplicates(items): + return iter(collections.OrderedDict.fromkeys(items)) + + +class FileReader(abc.TraversableResources): + def __init__(self, loader): + self.path = pathlib.Path(loader.path).parent + + def resource_path(self, resource): + """ + Return the file system path to prevent + `resources.path()` from creating a temporary + copy. + """ + return str(self.path.joinpath(resource)) + + def files(self): + return self.path + + +class ZipReader(abc.TraversableResources): + def __init__(self, loader, module): + _, _, name = module.rpartition('.') + self.prefix = loader.prefix.replace('\\', '/') + name + '/' + self.archive = loader.archive + + def open_resource(self, resource): + try: + return super().open_resource(resource) + except KeyError as exc: + raise FileNotFoundError(exc.args[0]) + + def is_resource(self, path): + # workaround for `zipfile.Path.is_file` returning true + # for non-existent paths. + target = self.files().joinpath(path) + return target.is_file() and target.exists() + + def files(self): + return ZipPath(self.archive, self.prefix) + + +class MultiplexedPath(abc.Traversable): + """ + Given a series of Traversable objects, implement a merged + version of the interface across all objects. Useful for + namespace packages which may be multihomed at a single + name. + """ + + def __init__(self, *paths): + self._paths = list(map(pathlib.Path, remove_duplicates(paths))) + if not self._paths: + message = 'MultiplexedPath must contain at least one path' + raise FileNotFoundError(message) + if not all(path.is_dir() for path in self._paths): + raise NotADirectoryError('MultiplexedPath only supports directories') + + def iterdir(self): + files = (file for path in self._paths for file in path.iterdir()) + return unique_everseen(files, key=operator.attrgetter('name')) + + def read_bytes(self): + raise FileNotFoundError(f'{self} is not a file') + + def read_text(self, *args, **kwargs): + raise FileNotFoundError(f'{self} is not a file') + + def is_dir(self): + return True + + def is_file(self): + return False + + def joinpath(self, *descendants): + try: + return super().joinpath(*descendants) + except abc.TraversalError: + # One of the paths did not resolve (a directory does not exist). + # Just return something that will not exist. + return self._paths[0].joinpath(*descendants) + + def open(self, *args, **kwargs): + raise FileNotFoundError(f'{self} is not a file') + + @property + def name(self): + return self._paths[0].name + + def __repr__(self): + paths = ', '.join(f"'{path}'" for path in self._paths) + return f'MultiplexedPath({paths})' + + +class NamespaceReader(abc.TraversableResources): + def __init__(self, namespace_path): + if 'NamespacePath' not in str(namespace_path): + raise ValueError('Invalid path') + self.path = MultiplexedPath(*list(namespace_path)) + + def resource_path(self, resource): + """ + Return the file system path to prevent + `resources.path()` from creating a temporary + copy. + """ + return str(self.path.joinpath(resource)) + + def files(self): + return self.path diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/simple.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/simple.py new file mode 100644 index 0000000000000000000000000000000000000000..7770c922c84fabe0031333a4de305dd6d6852911 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/importlib_resources/simple.py @@ -0,0 +1,106 @@ +""" +Interface adapters for low-level readers. +""" + +import abc +import io +import itertools +from typing import BinaryIO, List + +from .abc import Traversable, TraversableResources + + +class SimpleReader(abc.ABC): + """ + The minimum, low-level interface required from a resource + provider. + """ + + @property + @abc.abstractmethod + def package(self) -> str: + """ + The name of the package for which this reader loads resources. + """ + + @abc.abstractmethod + def children(self) -> List['SimpleReader']: + """ + Obtain an iterable of SimpleReader for available + child containers (e.g. directories). + """ + + @abc.abstractmethod + def resources(self) -> List[str]: + """ + Obtain available named resources for this virtual package. + """ + + @abc.abstractmethod + def open_binary(self, resource: str) -> BinaryIO: + """ + Obtain a File-like for a named resource. + """ + + @property + def name(self): + return self.package.split('.')[-1] + + +class ResourceContainer(Traversable): + """ + Traversable container for a package's resources via its reader. + """ + + def __init__(self, reader: SimpleReader): + self.reader = reader + + def is_dir(self): + return True + + def is_file(self): + return False + + def iterdir(self): + files = (ResourceHandle(self, name) for name in self.reader.resources) + dirs = map(ResourceContainer, self.reader.children()) + return itertools.chain(files, dirs) + + def open(self, *args, **kwargs): + raise IsADirectoryError() + + +class ResourceHandle(Traversable): + """ + Handle to a named resource in a ResourceReader. + """ + + def __init__(self, parent: ResourceContainer, name: str): + self.parent = parent + self.name = name # type: ignore + + def is_file(self): + return True + + def is_dir(self): + return False + + def open(self, mode='r', *args, **kwargs): + stream = self.parent.reader.open_binary(self.name) + if 'b' not in mode: + stream = io.TextIOWrapper(*args, **kwargs) + return stream + + def joinpath(self, name): + raise RuntimeError("Cannot traverse into a resource") + + +class TraversableReader(TraversableResources, SimpleReader): + """ + A TraversableResources based on SimpleReader. Resource providers + may derive from this class to provide the TraversableResources + interface by supplying the SimpleReader interface. + """ + + def files(self): + return ResourceContainer(self) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00e76cfe7a9660818f56585ac622f88012ce25a5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/context.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/context.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b19e198166e6b6e78d490d2e68526cc747eb3b7d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/__pycache__/context.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/context.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/context.py new file mode 100644 index 0000000000000000000000000000000000000000..c42f6135d5527b2e35df448667879df1c5d8bf19 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/context.py @@ -0,0 +1,361 @@ +from __future__ import annotations + +import contextlib +import functools +import operator +import os +import shutil +import subprocess +import sys +import tempfile +import urllib.request +import warnings +from typing import Iterator + + +if sys.version_info < (3, 12): + from pkg_resources.extern.backports import tarfile +else: + import tarfile + + +@contextlib.contextmanager +def pushd(dir: str | os.PathLike) -> Iterator[str | os.PathLike]: + """ + >>> tmp_path = getfixture('tmp_path') + >>> with pushd(tmp_path): + ... assert os.getcwd() == os.fspath(tmp_path) + >>> assert os.getcwd() != os.fspath(tmp_path) + """ + + orig = os.getcwd() + os.chdir(dir) + try: + yield dir + finally: + os.chdir(orig) + + +@contextlib.contextmanager +def tarball( + url, target_dir: str | os.PathLike | None = None +) -> Iterator[str | os.PathLike]: + """ + Get a tarball, extract it, yield, then clean up. + + >>> import urllib.request + >>> url = getfixture('tarfile_served') + >>> target = getfixture('tmp_path') / 'out' + >>> tb = tarball(url, target_dir=target) + >>> import pathlib + >>> with tb as extracted: + ... contents = pathlib.Path(extracted, 'contents.txt').read_text(encoding='utf-8') + >>> assert not os.path.exists(extracted) + """ + if target_dir is None: + target_dir = os.path.basename(url).replace('.tar.gz', '').replace('.tgz', '') + # In the tar command, use --strip-components=1 to strip the first path and + # then + # use -C to cause the files to be extracted to {target_dir}. This ensures + # that we always know where the files were extracted. + os.mkdir(target_dir) + try: + req = urllib.request.urlopen(url) + with tarfile.open(fileobj=req, mode='r|*') as tf: + tf.extractall(path=target_dir, filter=strip_first_component) + yield target_dir + finally: + shutil.rmtree(target_dir) + + +def strip_first_component( + member: tarfile.TarInfo, + path, +) -> tarfile.TarInfo: + _, member.name = member.name.split('/', 1) + return member + + +def _compose(*cmgrs): + """ + Compose any number of dependent context managers into a single one. + + The last, innermost context manager may take arbitrary arguments, but + each successive context manager should accept the result from the + previous as a single parameter. + + Like :func:`jaraco.functools.compose`, behavior works from right to + left, so the context manager should be indicated from outermost to + innermost. + + Example, to create a context manager to change to a temporary + directory: + + >>> temp_dir_as_cwd = _compose(pushd, temp_dir) + >>> with temp_dir_as_cwd() as dir: + ... assert os.path.samefile(os.getcwd(), dir) + """ + + def compose_two(inner, outer): + def composed(*args, **kwargs): + with inner(*args, **kwargs) as saved, outer(saved) as res: + yield res + + return contextlib.contextmanager(composed) + + return functools.reduce(compose_two, reversed(cmgrs)) + + +tarball_cwd = _compose(pushd, tarball) + + +@contextlib.contextmanager +def tarball_context(*args, **kwargs): + warnings.warn( + "tarball_context is deprecated. Use tarball or tarball_cwd instead.", + DeprecationWarning, + stacklevel=2, + ) + pushd_ctx = kwargs.pop('pushd', pushd) + with tarball(*args, **kwargs) as tball, pushd_ctx(tball) as dir: + yield dir + + +def infer_compression(url): + """ + Given a URL or filename, infer the compression code for tar. + + >>> infer_compression('http://foo/bar.tar.gz') + 'z' + >>> infer_compression('http://foo/bar.tgz') + 'z' + >>> infer_compression('file.bz') + 'j' + >>> infer_compression('file.xz') + 'J' + """ + warnings.warn( + "infer_compression is deprecated with no replacement", + DeprecationWarning, + stacklevel=2, + ) + # cheat and just assume it's the last two characters + compression_indicator = url[-2:] + mapping = dict(gz='z', bz='j', xz='J') + # Assume 'z' (gzip) if no match + return mapping.get(compression_indicator, 'z') + + +@contextlib.contextmanager +def temp_dir(remover=shutil.rmtree): + """ + Create a temporary directory context. Pass a custom remover + to override the removal behavior. + + >>> import pathlib + >>> with temp_dir() as the_dir: + ... assert os.path.isdir(the_dir) + ... _ = pathlib.Path(the_dir).joinpath('somefile').write_text('contents', encoding='utf-8') + >>> assert not os.path.exists(the_dir) + """ + temp_dir = tempfile.mkdtemp() + try: + yield temp_dir + finally: + remover(temp_dir) + + +@contextlib.contextmanager +def repo_context(url, branch=None, quiet=True, dest_ctx=temp_dir): + """ + Check out the repo indicated by url. + + If dest_ctx is supplied, it should be a context manager + to yield the target directory for the check out. + """ + exe = 'git' if 'git' in url else 'hg' + with dest_ctx() as repo_dir: + cmd = [exe, 'clone', url, repo_dir] + if branch: + cmd.extend(['--branch', branch]) + devnull = open(os.path.devnull, 'w') + stdout = devnull if quiet else None + subprocess.check_call(cmd, stdout=stdout) + yield repo_dir + + +def null(): + """ + A null context suitable to stand in for a meaningful context. + + >>> with null() as value: + ... assert value is None + + This context is most useful when dealing with two or more code + branches but only some need a context. Wrap the others in a null + context to provide symmetry across all options. + """ + warnings.warn( + "null is deprecated. Use contextlib.nullcontext", + DeprecationWarning, + stacklevel=2, + ) + return contextlib.nullcontext() + + +class ExceptionTrap: + """ + A context manager that will catch certain exceptions and provide an + indication they occurred. + + >>> with ExceptionTrap() as trap: + ... raise Exception() + >>> bool(trap) + True + + >>> with ExceptionTrap() as trap: + ... pass + >>> bool(trap) + False + + >>> with ExceptionTrap(ValueError) as trap: + ... raise ValueError("1 + 1 is not 3") + >>> bool(trap) + True + >>> trap.value + ValueError('1 + 1 is not 3') + >>> trap.tb + + + >>> with ExceptionTrap(ValueError) as trap: + ... raise Exception() + Traceback (most recent call last): + ... + Exception + + >>> bool(trap) + False + """ + + exc_info = None, None, None + + def __init__(self, exceptions=(Exception,)): + self.exceptions = exceptions + + def __enter__(self): + return self + + @property + def type(self): + return self.exc_info[0] + + @property + def value(self): + return self.exc_info[1] + + @property + def tb(self): + return self.exc_info[2] + + def __exit__(self, *exc_info): + type = exc_info[0] + matches = type and issubclass(type, self.exceptions) + if matches: + self.exc_info = exc_info + return matches + + def __bool__(self): + return bool(self.type) + + def raises(self, func, *, _test=bool): + """ + Wrap func and replace the result with the truth + value of the trap (True if an exception occurred). + + First, give the decorator an alias to support Python 3.8 + Syntax. + + >>> raises = ExceptionTrap(ValueError).raises + + Now decorate a function that always fails. + + >>> @raises + ... def fail(): + ... raise ValueError('failed') + >>> fail() + True + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + with ExceptionTrap(self.exceptions) as trap: + func(*args, **kwargs) + return _test(trap) + + return wrapper + + def passes(self, func): + """ + Wrap func and replace the result with the truth + value of the trap (True if no exception). + + First, give the decorator an alias to support Python 3.8 + Syntax. + + >>> passes = ExceptionTrap(ValueError).passes + + Now decorate a function that always fails. + + >>> @passes + ... def fail(): + ... raise ValueError('failed') + + >>> fail() + False + """ + return self.raises(func, _test=operator.not_) + + +class suppress(contextlib.suppress, contextlib.ContextDecorator): + """ + A version of contextlib.suppress with decorator support. + + >>> @suppress(KeyError) + ... def key_error(): + ... {}[''] + >>> key_error() + """ + + +class on_interrupt(contextlib.ContextDecorator): + """ + Replace a KeyboardInterrupt with SystemExit(1) + + >>> def do_interrupt(): + ... raise KeyboardInterrupt() + >>> on_interrupt('error')(do_interrupt)() + Traceback (most recent call last): + ... + SystemExit: 1 + >>> on_interrupt('error', code=255)(do_interrupt)() + Traceback (most recent call last): + ... + SystemExit: 255 + >>> on_interrupt('suppress')(do_interrupt)() + >>> with __import__('pytest').raises(KeyboardInterrupt): + ... on_interrupt('ignore')(do_interrupt)() + """ + + def __init__(self, action='error', /, code=1): + self.action = action + self.code = code + + def __enter__(self): + return self + + def __exit__(self, exctype, excinst, exctb): + if exctype is not KeyboardInterrupt or self.action == 'ignore': + return + elif self.action == 'error': + raise SystemExit(self.code) from excinst + return self.action == 'suppress' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f523099c7239013e3a7daf5268312d2e03cf374c --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.py @@ -0,0 +1,633 @@ +import collections.abc +import functools +import inspect +import itertools +import operator +import time +import types +import warnings + +import pkg_resources.extern.more_itertools + + +def compose(*funcs): + """ + Compose any number of unary functions into a single unary function. + + >>> import textwrap + >>> expected = str.strip(textwrap.dedent(compose.__doc__)) + >>> strip_and_dedent = compose(str.strip, textwrap.dedent) + >>> strip_and_dedent(compose.__doc__) == expected + True + + Compose also allows the innermost function to take arbitrary arguments. + + >>> round_three = lambda x: round(x, ndigits=3) + >>> f = compose(round_three, int.__truediv__) + >>> [f(3*x, x+1) for x in range(1,10)] + [1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7] + """ + + def compose_two(f1, f2): + return lambda *args, **kwargs: f1(f2(*args, **kwargs)) + + return functools.reduce(compose_two, funcs) + + +def once(func): + """ + Decorate func so it's only ever called the first time. + + This decorator can ensure that an expensive or non-idempotent function + will not be expensive on subsequent calls and is idempotent. + + >>> add_three = once(lambda a: a+3) + >>> add_three(3) + 6 + >>> add_three(9) + 6 + >>> add_three('12') + 6 + + To reset the stored value, simply clear the property ``saved_result``. + + >>> del add_three.saved_result + >>> add_three(9) + 12 + >>> add_three(8) + 12 + + Or invoke 'reset()' on it. + + >>> add_three.reset() + >>> add_three(-3) + 0 + >>> add_three(0) + 0 + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not hasattr(wrapper, 'saved_result'): + wrapper.saved_result = func(*args, **kwargs) + return wrapper.saved_result + + wrapper.reset = lambda: vars(wrapper).__delitem__('saved_result') + return wrapper + + +def method_cache(method, cache_wrapper=functools.lru_cache()): + """ + Wrap lru_cache to support storing the cache data in the object instances. + + Abstracts the common paradigm where the method explicitly saves an + underscore-prefixed protected property on first call and returns that + subsequently. + + >>> class MyClass: + ... calls = 0 + ... + ... @method_cache + ... def method(self, value): + ... self.calls += 1 + ... return value + + >>> a = MyClass() + >>> a.method(3) + 3 + >>> for x in range(75): + ... res = a.method(x) + >>> a.calls + 75 + + Note that the apparent behavior will be exactly like that of lru_cache + except that the cache is stored on each instance, so values in one + instance will not flush values from another, and when an instance is + deleted, so are the cached values for that instance. + + >>> b = MyClass() + >>> for x in range(35): + ... res = b.method(x) + >>> b.calls + 35 + >>> a.method(0) + 0 + >>> a.calls + 75 + + Note that if method had been decorated with ``functools.lru_cache()``, + a.calls would have been 76 (due to the cached value of 0 having been + flushed by the 'b' instance). + + Clear the cache with ``.cache_clear()`` + + >>> a.method.cache_clear() + + Same for a method that hasn't yet been called. + + >>> c = MyClass() + >>> c.method.cache_clear() + + Another cache wrapper may be supplied: + + >>> cache = functools.lru_cache(maxsize=2) + >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) + >>> a = MyClass() + >>> a.method2() + 3 + + Caution - do not subsequently wrap the method with another decorator, such + as ``@property``, which changes the semantics of the function. + + See also + http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ + for another implementation and additional justification. + """ + + def wrapper(self, *args, **kwargs): + # it's the first call, replace the method with a cached, bound method + bound_method = types.MethodType(method, self) + cached_method = cache_wrapper(bound_method) + setattr(self, method.__name__, cached_method) + return cached_method(*args, **kwargs) + + # Support cache clear even before cache has been created. + wrapper.cache_clear = lambda: None + + return _special_method_cache(method, cache_wrapper) or wrapper + + +def _special_method_cache(method, cache_wrapper): + """ + Because Python treats special methods differently, it's not + possible to use instance attributes to implement the cached + methods. + + Instead, install the wrapper method under a different name + and return a simple proxy to that wrapper. + + https://github.com/jaraco/jaraco.functools/issues/5 + """ + name = method.__name__ + special_names = '__getattr__', '__getitem__' + + if name not in special_names: + return None + + wrapper_name = '__cached' + name + + def proxy(self, /, *args, **kwargs): + if wrapper_name not in vars(self): + bound = types.MethodType(method, self) + cache = cache_wrapper(bound) + setattr(self, wrapper_name, cache) + else: + cache = getattr(self, wrapper_name) + return cache(*args, **kwargs) + + return proxy + + +def apply(transform): + """ + Decorate a function with a transform function that is + invoked on results returned from the decorated function. + + >>> @apply(reversed) + ... def get_numbers(start): + ... "doc for get_numbers" + ... return range(start, start+3) + >>> list(get_numbers(4)) + [6, 5, 4] + >>> get_numbers.__doc__ + 'doc for get_numbers' + """ + + def wrap(func): + return functools.wraps(func)(compose(transform, func)) + + return wrap + + +def result_invoke(action): + r""" + Decorate a function with an action function that is + invoked on the results returned from the decorated + function (for its side effect), then return the original + result. + + >>> @result_invoke(print) + ... def add_two(a, b): + ... return a + b + >>> x = add_two(2, 3) + 5 + >>> x + 5 + """ + + def wrap(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + result = func(*args, **kwargs) + action(result) + return result + + return wrapper + + return wrap + + +def invoke(f, /, *args, **kwargs): + """ + Call a function for its side effect after initialization. + + The benefit of using the decorator instead of simply invoking a function + after defining it is that it makes explicit the author's intent for the + function to be called immediately. Whereas if one simply calls the + function immediately, it's less obvious if that was intentional or + incidental. It also avoids repeating the name - the two actions, defining + the function and calling it immediately are modeled separately, but linked + by the decorator construct. + + The benefit of having a function construct (opposed to just invoking some + behavior inline) is to serve as a scope in which the behavior occurs. It + avoids polluting the global namespace with local variables, provides an + anchor on which to attach documentation (docstring), keeps the behavior + logically separated (instead of conceptually separated or not separated at + all), and provides potential to re-use the behavior for testing or other + purposes. + + This function is named as a pithy way to communicate, "call this function + primarily for its side effect", or "while defining this function, also + take it aside and call it". It exists because there's no Python construct + for "define and call" (nor should there be, as decorators serve this need + just fine). The behavior happens immediately and synchronously. + + >>> @invoke + ... def func(): print("called") + called + >>> func() + called + + Use functools.partial to pass parameters to the initial call + + >>> @functools.partial(invoke, name='bingo') + ... def func(name): print('called with', name) + called with bingo + """ + f(*args, **kwargs) + return f + + +class Throttler: + """Rate-limit a function (or other callable).""" + + def __init__(self, func, max_rate=float('Inf')): + if isinstance(func, Throttler): + func = func.func + self.func = func + self.max_rate = max_rate + self.reset() + + def reset(self): + self.last_called = 0 + + def __call__(self, *args, **kwargs): + self._wait() + return self.func(*args, **kwargs) + + def _wait(self): + """Ensure at least 1/max_rate seconds from last call.""" + elapsed = time.time() - self.last_called + must_wait = 1 / self.max_rate - elapsed + time.sleep(max(0, must_wait)) + self.last_called = time.time() + + def __get__(self, obj, owner=None): + return first_invoke(self._wait, functools.partial(self.func, obj)) + + +def first_invoke(func1, func2): + """ + Return a function that when invoked will invoke func1 without + any parameters (for its side effect) and then invoke func2 + with whatever parameters were passed, returning its result. + """ + + def wrapper(*args, **kwargs): + func1() + return func2(*args, **kwargs) + + return wrapper + + +method_caller = first_invoke( + lambda: warnings.warn( + '`jaraco.functools.method_caller` is deprecated, ' + 'use `operator.methodcaller` instead', + DeprecationWarning, + stacklevel=3, + ), + operator.methodcaller, +) + + +def retry_call(func, cleanup=lambda: None, retries=0, trap=()): + """ + Given a callable func, trap the indicated exceptions + for up to 'retries' times, invoking cleanup on the + exception. On the final attempt, allow any exceptions + to propagate. + """ + attempts = itertools.count() if retries == float('inf') else range(retries) + for _ in attempts: + try: + return func() + except trap: + cleanup() + + return func() + + +def retry(*r_args, **r_kwargs): + """ + Decorator wrapper for retry_call. Accepts arguments to retry_call + except func and then returns a decorator for the decorated function. + + Ex: + + >>> @retry(retries=3) + ... def my_func(a, b): + ... "this is my funk" + ... print(a, b) + >>> my_func.__doc__ + 'this is my funk' + """ + + def decorate(func): + @functools.wraps(func) + def wrapper(*f_args, **f_kwargs): + bound = functools.partial(func, *f_args, **f_kwargs) + return retry_call(bound, *r_args, **r_kwargs) + + return wrapper + + return decorate + + +def print_yielded(func): + """ + Convert a generator into a function that prints all yielded elements. + + >>> @print_yielded + ... def x(): + ... yield 3; yield None + >>> x() + 3 + None + """ + print_all = functools.partial(map, print) + print_results = compose(more_itertools.consume, print_all, func) + return functools.wraps(func)(print_results) + + +def pass_none(func): + """ + Wrap func so it's not called if its first param is None. + + >>> print_text = pass_none(print) + >>> print_text('text') + text + >>> print_text(None) + """ + + @functools.wraps(func) + def wrapper(param, /, *args, **kwargs): + if param is not None: + return func(param, *args, **kwargs) + return None + + return wrapper + + +def assign_params(func, namespace): + """ + Assign parameters from namespace where func solicits. + + >>> def func(x, y=3): + ... print(x, y) + >>> assigned = assign_params(func, dict(x=2, z=4)) + >>> assigned() + 2 3 + + The usual errors are raised if a function doesn't receive + its required parameters: + + >>> assigned = assign_params(func, dict(y=3, z=4)) + >>> assigned() + Traceback (most recent call last): + TypeError: func() ...argument... + + It even works on methods: + + >>> class Handler: + ... def meth(self, arg): + ... print(arg) + >>> assign_params(Handler().meth, dict(arg='crystal', foo='clear'))() + crystal + """ + sig = inspect.signature(func) + params = sig.parameters.keys() + call_ns = {k: namespace[k] for k in params if k in namespace} + return functools.partial(func, **call_ns) + + +def save_method_args(method): + """ + Wrap a method such that when it is called, the args and kwargs are + saved on the method. + + >>> class MyClass: + ... @save_method_args + ... def method(self, a, b): + ... print(a, b) + >>> my_ob = MyClass() + >>> my_ob.method(1, 2) + 1 2 + >>> my_ob._saved_method.args + (1, 2) + >>> my_ob._saved_method.kwargs + {} + >>> my_ob.method(a=3, b='foo') + 3 foo + >>> my_ob._saved_method.args + () + >>> my_ob._saved_method.kwargs == dict(a=3, b='foo') + True + + The arguments are stored on the instance, allowing for + different instance to save different args. + + >>> your_ob = MyClass() + >>> your_ob.method({str('x'): 3}, b=[4]) + {'x': 3} [4] + >>> your_ob._saved_method.args + ({'x': 3},) + >>> my_ob._saved_method.args + () + """ + args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs') + + @functools.wraps(method) + def wrapper(self, /, *args, **kwargs): + attr_name = '_saved_' + method.__name__ + attr = args_and_kwargs(args, kwargs) + setattr(self, attr_name, attr) + return method(self, *args, **kwargs) + + return wrapper + + +def except_(*exceptions, replace=None, use=None): + """ + Replace the indicated exceptions, if raised, with the indicated + literal replacement or evaluated expression (if present). + + >>> safe_int = except_(ValueError)(int) + >>> safe_int('five') + >>> safe_int('5') + 5 + + Specify a literal replacement with ``replace``. + + >>> safe_int_r = except_(ValueError, replace=0)(int) + >>> safe_int_r('five') + 0 + + Provide an expression to ``use`` to pass through particular parameters. + + >>> safe_int_pt = except_(ValueError, use='args[0]')(int) + >>> safe_int_pt('five') + 'five' + + """ + + def decorate(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except exceptions: + try: + return eval(use) + except TypeError: + return replace + + return wrapper + + return decorate + + +def identity(x): + """ + Return the argument. + + >>> o = object() + >>> identity(o) is o + True + """ + return x + + +def bypass_when(check, *, _op=identity): + """ + Decorate a function to return its parameter when ``check``. + + >>> bypassed = [] # False + + >>> @bypass_when(bypassed) + ... def double(x): + ... return x * 2 + >>> double(2) + 4 + >>> bypassed[:] = [object()] # True + >>> double(2) + 2 + """ + + def decorate(func): + @functools.wraps(func) + def wrapper(param, /): + return param if _op(check) else func(param) + + return wrapper + + return decorate + + +def bypass_unless(check): + """ + Decorate a function to return its parameter unless ``check``. + + >>> enabled = [object()] # True + + >>> @bypass_unless(enabled) + ... def double(x): + ... return x * 2 + >>> double(2) + 4 + >>> del enabled[:] # False + >>> double(2) + 2 + """ + return bypass_when(check, _op=operator.not_) + + +@functools.singledispatch +def _splat_inner(args, func): + """Splat args to func.""" + return func(*args) + + +@_splat_inner.register +def _(args: collections.abc.Mapping, func): + """Splat kargs to func as kwargs.""" + return func(**args) + + +def splat(func): + """ + Wrap func to expect its parameters to be passed positionally in a tuple. + + Has a similar effect to that of ``itertools.starmap`` over + simple ``map``. + + >>> pairs = [(-1, 1), (0, 2)] + >>> pkg_resources.extern.more_itertools.consume(itertools.starmap(print, pairs)) + -1 1 + 0 2 + >>> pkg_resources.extern.more_itertools.consume(map(splat(print), pairs)) + -1 1 + 0 2 + + The approach generalizes to other iterators that don't have a "star" + equivalent, such as a "starfilter". + + >>> list(filter(splat(operator.add), pairs)) + [(0, 2)] + + Splat also accepts a mapping argument. + + >>> def is_nice(msg, code): + ... return "smile" in msg or code == 0 + >>> msgs = [ + ... dict(msg='smile!', code=20), + ... dict(msg='error :(', code=1), + ... dict(msg='unknown', code=0), + ... ] + >>> for msg in filter(splat(is_nice), msgs): + ... print(msg) + {'msg': 'smile!', 'code': 20} + {'msg': 'unknown', 'code': 0} + """ + return functools.wraps(func)(functools.partial(_splat_inner, func=func)) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..c2b9ab1757ec31dc7ccf13a9eaf8148ba0f31cec --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__init__.pyi @@ -0,0 +1,128 @@ +from collections.abc import Callable, Hashable, Iterator +from functools import partial +from operator import methodcaller +import sys +from typing import ( + Any, + Generic, + Protocol, + TypeVar, + overload, +) + +if sys.version_info >= (3, 10): + from typing import Concatenate, ParamSpec +else: + from typing_extensions import Concatenate, ParamSpec + +_P = ParamSpec('_P') +_R = TypeVar('_R') +_T = TypeVar('_T') +_R1 = TypeVar('_R1') +_R2 = TypeVar('_R2') +_V = TypeVar('_V') +_S = TypeVar('_S') +_R_co = TypeVar('_R_co', covariant=True) + +class _OnceCallable(Protocol[_P, _R]): + saved_result: _R + reset: Callable[[], None] + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ... + +class _ProxyMethodCacheWrapper(Protocol[_R_co]): + cache_clear: Callable[[], None] + def __call__(self, *args: Hashable, **kwargs: Hashable) -> _R_co: ... + +class _MethodCacheWrapper(Protocol[_R_co]): + def cache_clear(self) -> None: ... + def __call__(self, *args: Hashable, **kwargs: Hashable) -> _R_co: ... + +# `compose()` overloads below will cover most use cases. + +@overload +def compose( + __func1: Callable[[_R], _T], + __func2: Callable[_P, _R], + /, +) -> Callable[_P, _T]: ... +@overload +def compose( + __func1: Callable[[_R], _T], + __func2: Callable[[_R1], _R], + __func3: Callable[_P, _R1], + /, +) -> Callable[_P, _T]: ... +@overload +def compose( + __func1: Callable[[_R], _T], + __func2: Callable[[_R2], _R], + __func3: Callable[[_R1], _R2], + __func4: Callable[_P, _R1], + /, +) -> Callable[_P, _T]: ... +def once(func: Callable[_P, _R]) -> _OnceCallable[_P, _R]: ... +def method_cache( + method: Callable[..., _R], + cache_wrapper: Callable[[Callable[..., _R]], _MethodCacheWrapper[_R]] = ..., +) -> _MethodCacheWrapper[_R] | _ProxyMethodCacheWrapper[_R]: ... +def apply( + transform: Callable[[_R], _T] +) -> Callable[[Callable[_P, _R]], Callable[_P, _T]]: ... +def result_invoke( + action: Callable[[_R], Any] +) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... +def invoke( + f: Callable[_P, _R], /, *args: _P.args, **kwargs: _P.kwargs +) -> Callable[_P, _R]: ... +def call_aside( + f: Callable[_P, _R], *args: _P.args, **kwargs: _P.kwargs +) -> Callable[_P, _R]: ... + +class Throttler(Generic[_R]): + last_called: float + func: Callable[..., _R] + max_rate: float + def __init__( + self, func: Callable[..., _R] | Throttler[_R], max_rate: float = ... + ) -> None: ... + def reset(self) -> None: ... + def __call__(self, *args: Any, **kwargs: Any) -> _R: ... + def __get__(self, obj: Any, owner: type[Any] | None = ...) -> Callable[..., _R]: ... + +def first_invoke( + func1: Callable[..., Any], func2: Callable[_P, _R] +) -> Callable[_P, _R]: ... + +method_caller: Callable[..., methodcaller] + +def retry_call( + func: Callable[..., _R], + cleanup: Callable[..., None] = ..., + retries: int | float = ..., + trap: type[BaseException] | tuple[type[BaseException], ...] = ..., +) -> _R: ... +def retry( + cleanup: Callable[..., None] = ..., + retries: int | float = ..., + trap: type[BaseException] | tuple[type[BaseException], ...] = ..., +) -> Callable[[Callable[..., _R]], Callable[..., _R]]: ... +def print_yielded(func: Callable[_P, Iterator[Any]]) -> Callable[_P, None]: ... +def pass_none( + func: Callable[Concatenate[_T, _P], _R] +) -> Callable[Concatenate[_T, _P], _R]: ... +def assign_params( + func: Callable[..., _R], namespace: dict[str, Any] +) -> partial[_R]: ... +def save_method_args( + method: Callable[Concatenate[_S, _P], _R] +) -> Callable[Concatenate[_S, _P], _R]: ... +def except_( + *exceptions: type[BaseException], replace: Any = ..., use: Any = ... +) -> Callable[[Callable[_P, Any]], Callable[_P, Any]]: ... +def identity(x: _T) -> _T: ... +def bypass_when( + check: _V, *, _op: Callable[[_V], Any] = ... +) -> Callable[[Callable[[_T], _R]], Callable[[_T], _T | _R]]: ... +def bypass_unless( + check: Any, +) -> Callable[[Callable[[_T], _R]], Callable[[_T], _T | _R]]: ... diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39ad23844b9c47e7c28ce254bcad03a88e5d6e3d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/py.typed b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/functools/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c466378ceba69a335d2beb4d3af92703d52b3831 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py @@ -0,0 +1,599 @@ +import re +import itertools +import textwrap +import functools + +try: + from importlib.resources import files # type: ignore +except ImportError: # pragma: nocover + from pkg_resources.extern.importlib_resources import files # type: ignore + +from pkg_resources.extern.jaraco.functools import compose, method_cache +from pkg_resources.extern.jaraco.context import ExceptionTrap + + +def substitution(old, new): + """ + Return a function that will perform a substitution on a string + """ + return lambda s: s.replace(old, new) + + +def multi_substitution(*substitutions): + """ + Take a sequence of pairs specifying substitutions, and create + a function that performs those substitutions. + + >>> multi_substitution(('foo', 'bar'), ('bar', 'baz'))('foo') + 'baz' + """ + substitutions = itertools.starmap(substitution, substitutions) + # compose function applies last function first, so reverse the + # substitutions to get the expected order. + substitutions = reversed(tuple(substitutions)) + return compose(*substitutions) + + +class FoldedCase(str): + """ + A case insensitive string class; behaves just like str + except compares equal when the only variation is case. + + >>> s = FoldedCase('hello world') + + >>> s == 'Hello World' + True + + >>> 'Hello World' == s + True + + >>> s != 'Hello World' + False + + >>> s.index('O') + 4 + + >>> s.split('O') + ['hell', ' w', 'rld'] + + >>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])) + ['alpha', 'Beta', 'GAMMA'] + + Sequence membership is straightforward. + + >>> "Hello World" in [s] + True + >>> s in ["Hello World"] + True + + You may test for set inclusion, but candidate and elements + must both be folded. + + >>> FoldedCase("Hello World") in {s} + True + >>> s in {FoldedCase("Hello World")} + True + + String inclusion works as long as the FoldedCase object + is on the right. + + >>> "hello" in FoldedCase("Hello World") + True + + But not if the FoldedCase object is on the left: + + >>> FoldedCase('hello') in 'Hello World' + False + + In that case, use ``in_``: + + >>> FoldedCase('hello').in_('Hello World') + True + + >>> FoldedCase('hello') > FoldedCase('Hello') + False + """ + + def __lt__(self, other): + return self.lower() < other.lower() + + def __gt__(self, other): + return self.lower() > other.lower() + + def __eq__(self, other): + return self.lower() == other.lower() + + def __ne__(self, other): + return self.lower() != other.lower() + + def __hash__(self): + return hash(self.lower()) + + def __contains__(self, other): + return super().lower().__contains__(other.lower()) + + def in_(self, other): + "Does self appear in other?" + return self in FoldedCase(other) + + # cache lower since it's likely to be called frequently. + @method_cache + def lower(self): + return super().lower() + + def index(self, sub): + return self.lower().index(sub.lower()) + + def split(self, splitter=' ', maxsplit=0): + pattern = re.compile(re.escape(splitter), re.I) + return pattern.split(self, maxsplit) + + +# Python 3.8 compatibility +_unicode_trap = ExceptionTrap(UnicodeDecodeError) + + +@_unicode_trap.passes +def is_decodable(value): + r""" + Return True if the supplied value is decodable (using the default + encoding). + + >>> is_decodable(b'\xff') + False + >>> is_decodable(b'\x32') + True + """ + value.decode() + + +def is_binary(value): + r""" + Return True if the value appears to be binary (that is, it's a byte + string and isn't decodable). + + >>> is_binary(b'\xff') + True + >>> is_binary('\xff') + False + """ + return isinstance(value, bytes) and not is_decodable(value) + + +def trim(s): + r""" + Trim something like a docstring to remove the whitespace that + is common due to indentation and formatting. + + >>> trim("\n\tfoo = bar\n\t\tbar = baz\n") + 'foo = bar\n\tbar = baz' + """ + return textwrap.dedent(s).strip() + + +def wrap(s): + """ + Wrap lines of text, retaining existing newlines as + paragraph markers. + + >>> print(wrap(lorem_ipsum)) + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad + minim veniam, quis nostrud exercitation ullamco laboris nisi ut + aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla + pariatur. Excepteur sint occaecat cupidatat non proident, sunt in + culpa qui officia deserunt mollit anim id est laborum. + + Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam + varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus + magna felis sollicitudin mauris. Integer in mauris eu nibh euismod + gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis + risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, + eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas + fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla + a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, + neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing + sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque + nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus + quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, + molestie eu, feugiat in, orci. In hac habitasse platea dictumst. + """ + paragraphs = s.splitlines() + wrapped = ('\n'.join(textwrap.wrap(para)) for para in paragraphs) + return '\n\n'.join(wrapped) + + +def unwrap(s): + r""" + Given a multi-line string, return an unwrapped version. + + >>> wrapped = wrap(lorem_ipsum) + >>> wrapped.count('\n') + 20 + >>> unwrapped = unwrap(wrapped) + >>> unwrapped.count('\n') + 1 + >>> print(unwrapped) + Lorem ipsum dolor sit amet, consectetur adipiscing ... + Curabitur pretium tincidunt lacus. Nulla gravida orci ... + + """ + paragraphs = re.split(r'\n\n+', s) + cleaned = (para.replace('\n', ' ') for para in paragraphs) + return '\n'.join(cleaned) + + + + +class Splitter(object): + """object that will split a string with the given arguments for each call + + >>> s = Splitter(',') + >>> s('hello, world, this is your, master calling') + ['hello', ' world', ' this is your', ' master calling'] + """ + + def __init__(self, *args): + self.args = args + + def __call__(self, s): + return s.split(*self.args) + + +def indent(string, prefix=' ' * 4): + """ + >>> indent('foo') + ' foo' + """ + return prefix + string + + +class WordSet(tuple): + """ + Given an identifier, return the words that identifier represents, + whether in camel case, underscore-separated, etc. + + >>> WordSet.parse("camelCase") + ('camel', 'Case') + + >>> WordSet.parse("under_sep") + ('under', 'sep') + + Acronyms should be retained + + >>> WordSet.parse("firstSNL") + ('first', 'SNL') + + >>> WordSet.parse("you_and_I") + ('you', 'and', 'I') + + >>> WordSet.parse("A simple test") + ('A', 'simple', 'test') + + Multiple caps should not interfere with the first cap of another word. + + >>> WordSet.parse("myABCClass") + ('my', 'ABC', 'Class') + + The result is a WordSet, so you can get the form you need. + + >>> WordSet.parse("myABCClass").underscore_separated() + 'my_ABC_Class' + + >>> WordSet.parse('a-command').camel_case() + 'ACommand' + + >>> WordSet.parse('someIdentifier').lowered().space_separated() + 'some identifier' + + Slices of the result should return another WordSet. + + >>> WordSet.parse('taken-out-of-context')[1:].underscore_separated() + 'out_of_context' + + >>> WordSet.from_class_name(WordSet()).lowered().space_separated() + 'word set' + + >>> example = WordSet.parse('figured it out') + >>> example.headless_camel_case() + 'figuredItOut' + >>> example.dash_separated() + 'figured-it-out' + + """ + + _pattern = re.compile('([A-Z]?[a-z]+)|([A-Z]+(?![a-z]))') + + def capitalized(self): + return WordSet(word.capitalize() for word in self) + + def lowered(self): + return WordSet(word.lower() for word in self) + + def camel_case(self): + return ''.join(self.capitalized()) + + def headless_camel_case(self): + words = iter(self) + first = next(words).lower() + new_words = itertools.chain((first,), WordSet(words).camel_case()) + return ''.join(new_words) + + def underscore_separated(self): + return '_'.join(self) + + def dash_separated(self): + return '-'.join(self) + + def space_separated(self): + return ' '.join(self) + + def trim_right(self, item): + """ + Remove the item from the end of the set. + + >>> WordSet.parse('foo bar').trim_right('foo') + ('foo', 'bar') + >>> WordSet.parse('foo bar').trim_right('bar') + ('foo',) + >>> WordSet.parse('').trim_right('bar') + () + """ + return self[:-1] if self and self[-1] == item else self + + def trim_left(self, item): + """ + Remove the item from the beginning of the set. + + >>> WordSet.parse('foo bar').trim_left('foo') + ('bar',) + >>> WordSet.parse('foo bar').trim_left('bar') + ('foo', 'bar') + >>> WordSet.parse('').trim_left('bar') + () + """ + return self[1:] if self and self[0] == item else self + + def trim(self, item): + """ + >>> WordSet.parse('foo bar').trim('foo') + ('bar',) + """ + return self.trim_left(item).trim_right(item) + + def __getitem__(self, item): + result = super(WordSet, self).__getitem__(item) + if isinstance(item, slice): + result = WordSet(result) + return result + + @classmethod + def parse(cls, identifier): + matches = cls._pattern.finditer(identifier) + return WordSet(match.group(0) for match in matches) + + @classmethod + def from_class_name(cls, subject): + return cls.parse(subject.__class__.__name__) + + +# for backward compatibility +words = WordSet.parse + + +def simple_html_strip(s): + r""" + Remove HTML from the string `s`. + + >>> str(simple_html_strip('')) + '' + + >>> print(simple_html_strip('A stormy day in paradise')) + A stormy day in paradise + + >>> print(simple_html_strip('Somebody tell the truth.')) + Somebody tell the truth. + + >>> print(simple_html_strip('What about
\nmultiple lines?')) + What about + multiple lines? + """ + html_stripper = re.compile('()|(<[^>]*>)|([^<]+)', re.DOTALL) + texts = (match.group(3) or '' for match in html_stripper.finditer(s)) + return ''.join(texts) + + +class SeparatedValues(str): + """ + A string separated by a separator. Overrides __iter__ for getting + the values. + + >>> list(SeparatedValues('a,b,c')) + ['a', 'b', 'c'] + + Whitespace is stripped and empty values are discarded. + + >>> list(SeparatedValues(' a, b , c, ')) + ['a', 'b', 'c'] + """ + + separator = ',' + + def __iter__(self): + parts = self.split(self.separator) + return filter(None, (part.strip() for part in parts)) + + +class Stripper: + r""" + Given a series of lines, find the common prefix and strip it from them. + + >>> lines = [ + ... 'abcdefg\n', + ... 'abc\n', + ... 'abcde\n', + ... ] + >>> res = Stripper.strip_prefix(lines) + >>> res.prefix + 'abc' + >>> list(res.lines) + ['defg\n', '\n', 'de\n'] + + If no prefix is common, nothing should be stripped. + + >>> lines = [ + ... 'abcd\n', + ... '1234\n', + ... ] + >>> res = Stripper.strip_prefix(lines) + >>> res.prefix = '' + >>> list(res.lines) + ['abcd\n', '1234\n'] + """ + + def __init__(self, prefix, lines): + self.prefix = prefix + self.lines = map(self, lines) + + @classmethod + def strip_prefix(cls, lines): + prefix_lines, lines = itertools.tee(lines) + prefix = functools.reduce(cls.common_prefix, prefix_lines) + return cls(prefix, lines) + + def __call__(self, line): + if not self.prefix: + return line + null, prefix, rest = line.partition(self.prefix) + return rest + + @staticmethod + def common_prefix(s1, s2): + """ + Return the common prefix of two lines. + """ + index = min(len(s1), len(s2)) + while s1[:index] != s2[:index]: + index -= 1 + return s1[:index] + + +def remove_prefix(text, prefix): + """ + Remove the prefix from the text if it exists. + + >>> remove_prefix('underwhelming performance', 'underwhelming ') + 'performance' + + >>> remove_prefix('something special', 'sample') + 'something special' + """ + null, prefix, rest = text.rpartition(prefix) + return rest + + +def remove_suffix(text, suffix): + """ + Remove the suffix from the text if it exists. + + >>> remove_suffix('name.git', '.git') + 'name' + + >>> remove_suffix('something special', 'sample') + 'something special' + """ + rest, suffix, null = text.partition(suffix) + return rest + + +def normalize_newlines(text): + r""" + Replace alternate newlines with the canonical newline. + + >>> normalize_newlines('Lorem Ipsum\u2029') + 'Lorem Ipsum\n' + >>> normalize_newlines('Lorem Ipsum\r\n') + 'Lorem Ipsum\n' + >>> normalize_newlines('Lorem Ipsum\x85') + 'Lorem Ipsum\n' + """ + newlines = ['\r\n', '\r', '\n', '\u0085', '\u2028', '\u2029'] + pattern = '|'.join(newlines) + return re.sub(pattern, '\n', text) + + +def _nonblank(str): + return str and not str.startswith('#') + + +@functools.singledispatch +def yield_lines(iterable): + r""" + Yield valid lines of a string or iterable. + + >>> list(yield_lines('')) + [] + >>> list(yield_lines(['foo', 'bar'])) + ['foo', 'bar'] + >>> list(yield_lines('foo\nbar')) + ['foo', 'bar'] + >>> list(yield_lines('\nfoo\n#bar\nbaz #comment')) + ['foo', 'baz #comment'] + >>> list(yield_lines(['foo\nbar', 'baz', 'bing\n\n\n'])) + ['foo', 'bar', 'baz', 'bing'] + """ + return itertools.chain.from_iterable(map(yield_lines, iterable)) + + +@yield_lines.register(str) +def _(text): + return filter(_nonblank, map(str.strip, text.splitlines())) + + +def drop_comment(line): + """ + Drop comments. + + >>> drop_comment('foo # bar') + 'foo' + + A hash without a space may be in a URL. + + >>> drop_comment('http://example.com/foo#bar') + 'http://example.com/foo#bar' + """ + return line.partition(' #')[0] + + +def join_continuation(lines): + r""" + Join lines continued by a trailing backslash. + + >>> list(join_continuation(['foo \\', 'bar', 'baz'])) + ['foobar', 'baz'] + >>> list(join_continuation(['foo \\', 'bar', 'baz'])) + ['foobar', 'baz'] + >>> list(join_continuation(['foo \\', 'bar \\', 'baz'])) + ['foobarbaz'] + + Not sure why, but... + The character preceeding the backslash is also elided. + + >>> list(join_continuation(['goo\\', 'dly'])) + ['godly'] + + A terrible idea, but... + If no line is available to continue, suppress the lines. + + >>> list(join_continuation(['foo', 'bar\\', 'baz\\'])) + ['foo'] + """ + lines = iter(lines) + for item in lines: + while item.endswith('\\'): + try: + item = item[:-2].strip() + next(lines) + except StopIteration: + return + yield item diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1eff26b4c551a03e6b1cb39e7d6e898766398ad5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/jaraco/text/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..aff94a9abd02da42c8c012ea973ac8e812a52284 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.py @@ -0,0 +1,6 @@ +"""More routines for operating on iterables, beyond itertools""" + +from .more import * # noqa +from .recipes import * # noqa + +__version__ = '10.2.0' diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..96f6e36c7f4ac9ea0aebdcd9e11b8d1ff092d2ef --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__init__.pyi @@ -0,0 +1,2 @@ +from .more import * +from .recipes import * diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f93f1504ce6e433385f4a1190b1c901acfa091d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/recipes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/recipes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea1f84763cc56fe103b7801f8dba018d3067e54c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/__pycache__/recipes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.py new file mode 100644 index 0000000000000000000000000000000000000000..d0957681f54a6be78d45a509ad33ccf6ef3f4653 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.py @@ -0,0 +1,4655 @@ +import warnings + +from collections import Counter, defaultdict, deque, abc +from collections.abc import Sequence +from functools import cached_property, partial, reduce, wraps +from heapq import heapify, heapreplace, heappop +from itertools import ( + chain, + compress, + count, + cycle, + dropwhile, + groupby, + islice, + repeat, + starmap, + takewhile, + tee, + zip_longest, + product, +) +from math import exp, factorial, floor, log, perm, comb +from queue import Empty, Queue +from random import random, randrange, uniform +from operator import itemgetter, mul, sub, gt, lt, ge, le +from sys import hexversion, maxsize +from time import monotonic + +from .recipes import ( + _marker, + _zip_equal, + UnequalIterablesError, + consume, + flatten, + pairwise, + powerset, + take, + unique_everseen, + all_equal, + batched, +) + +__all__ = [ + 'AbortThread', + 'SequenceView', + 'UnequalIterablesError', + 'adjacent', + 'all_unique', + 'always_iterable', + 'always_reversible', + 'bucket', + 'callback_iter', + 'chunked', + 'chunked_even', + 'circular_shifts', + 'collapse', + 'combination_index', + 'combination_with_replacement_index', + 'consecutive_groups', + 'constrained_batches', + 'consumer', + 'count_cycle', + 'countable', + 'difference', + 'distinct_combinations', + 'distinct_permutations', + 'distribute', + 'divide', + 'duplicates_everseen', + 'duplicates_justseen', + 'classify_unique', + 'exactly_n', + 'filter_except', + 'filter_map', + 'first', + 'gray_product', + 'groupby_transform', + 'ichunked', + 'iequals', + 'ilen', + 'interleave', + 'interleave_evenly', + 'interleave_longest', + 'intersperse', + 'is_sorted', + 'islice_extended', + 'iterate', + 'iter_suppress', + 'last', + 'locate', + 'longest_common_prefix', + 'lstrip', + 'make_decorator', + 'map_except', + 'map_if', + 'map_reduce', + 'mark_ends', + 'minmax', + 'nth_or_last', + 'nth_permutation', + 'nth_product', + 'nth_combination_with_replacement', + 'numeric_range', + 'one', + 'only', + 'outer_product', + 'padded', + 'partial_product', + 'partitions', + 'peekable', + 'permutation_index', + 'product_index', + 'raise_', + 'repeat_each', + 'repeat_last', + 'replace', + 'rlocate', + 'rstrip', + 'run_length', + 'sample', + 'seekable', + 'set_partitions', + 'side_effect', + 'sliced', + 'sort_together', + 'split_after', + 'split_at', + 'split_before', + 'split_into', + 'split_when', + 'spy', + 'stagger', + 'strip', + 'strictly_n', + 'substrings', + 'substrings_indexes', + 'takewhile_inclusive', + 'time_limited', + 'unique_in_window', + 'unique_to_each', + 'unzip', + 'value_chain', + 'windowed', + 'windowed_complete', + 'with_iter', + 'zip_broadcast', + 'zip_equal', + 'zip_offset', +] + + +def chunked(iterable, n, strict=False): + """Break *iterable* into lists of length *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6], 3)) + [[1, 2, 3], [4, 5, 6]] + + By the default, the last yielded list will have fewer than *n* elements + if the length of *iterable* is not divisible by *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)) + [[1, 2, 3], [4, 5, 6], [7, 8]] + + To use a fill-in value instead, see the :func:`grouper` recipe. + + If the length of *iterable* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + list is yielded. + + """ + iterator = iter(partial(take, n, iter(iterable)), []) + if strict: + if n is None: + raise ValueError('n must not be None when using strict mode.') + + def ret(): + for chunk in iterator: + if len(chunk) != n: + raise ValueError('iterable is not divisible by n.') + yield chunk + + return iter(ret()) + else: + return iterator + + +def first(iterable, default=_marker): + """Return the first item of *iterable*, or *default* if *iterable* is + empty. + + >>> first([0, 1, 2, 3]) + 0 + >>> first([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + + :func:`first` is useful when you have a generator of expensive-to-retrieve + values and want any arbitrary one. It is marginally shorter than + ``next(iter(iterable), default)``. + + """ + for item in iterable: + return item + if default is _marker: + raise ValueError( + 'first() was called on an empty iterable, and no ' + 'default value was provided.' + ) + return default + + +def last(iterable, default=_marker): + """Return the last item of *iterable*, or *default* if *iterable* is + empty. + + >>> last([0, 1, 2, 3]) + 3 + >>> last([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + try: + if isinstance(iterable, Sequence): + return iterable[-1] + # Work around https://bugs.python.org/issue38525 + elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0): + return next(reversed(iterable)) + else: + return deque(iterable, maxlen=1)[-1] + except (IndexError, TypeError, StopIteration): + if default is _marker: + raise ValueError( + 'last() was called on an empty iterable, and no default was ' + 'provided.' + ) + return default + + +def nth_or_last(iterable, n, default=_marker): + """Return the nth or the last item of *iterable*, + or *default* if *iterable* is empty. + + >>> nth_or_last([0, 1, 2, 3], 2) + 2 + >>> nth_or_last([0, 1], 2) + 1 + >>> nth_or_last([], 0, 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + return last(islice(iterable, n + 1), default=default) + + +class peekable: + """Wrap an iterator to allow lookahead and prepending elements. + + Call :meth:`peek` on the result to get the value that will be returned + by :func:`next`. This won't advance the iterator: + + >>> p = peekable(['a', 'b']) + >>> p.peek() + 'a' + >>> next(p) + 'a' + + Pass :meth:`peek` a default value to return that instead of raising + ``StopIteration`` when the iterator is exhausted. + + >>> p = peekable([]) + >>> p.peek('hi') + 'hi' + + peekables also offer a :meth:`prepend` method, which "inserts" items + at the head of the iterable: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> p.peek() + 11 + >>> list(p) + [11, 12, 1, 2, 3] + + peekables can be indexed. Index 0 is the item that will be returned by + :func:`next`, index 1 is the item after that, and so on: + The values up to the given index will be cached. + + >>> p = peekable(['a', 'b', 'c', 'd']) + >>> p[0] + 'a' + >>> p[1] + 'b' + >>> next(p) + 'a' + + Negative indexes are supported, but be aware that they will cache the + remaining items in the source iterator, which may require significant + storage. + + To check whether a peekable is exhausted, check its truth value: + + >>> p = peekable(['a', 'b']) + >>> if p: # peekable has items + ... list(p) + ['a', 'b'] + >>> if not p: # peekable is exhausted + ... list(p) + [] + + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self._cache = deque() + + def __iter__(self): + return self + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + """Return the item that will be next returned from ``next()``. + + Return ``default`` if there are no items left. If ``default`` is not + provided, raise ``StopIteration``. + + """ + if not self._cache: + try: + self._cache.append(next(self._it)) + except StopIteration: + if default is _marker: + raise + return default + return self._cache[0] + + def prepend(self, *items): + """Stack up items to be the next ones returned from ``next()`` or + ``self.peek()``. The items will be returned in + first in, first out order:: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> list(p) + [11, 12, 1, 2, 3] + + It is possible, by prepending items, to "resurrect" a peekable that + previously raised ``StopIteration``. + + >>> p = peekable([]) + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + >>> p.prepend(1) + >>> next(p) + 1 + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + + """ + self._cache.extendleft(reversed(items)) + + def __next__(self): + if self._cache: + return self._cache.popleft() + + return next(self._it) + + def _get_slice(self, index): + # Normalize the slice's arguments + step = 1 if (index.step is None) else index.step + if step > 0: + start = 0 if (index.start is None) else index.start + stop = maxsize if (index.stop is None) else index.stop + elif step < 0: + start = -1 if (index.start is None) else index.start + stop = (-maxsize - 1) if (index.stop is None) else index.stop + else: + raise ValueError('slice step cannot be zero') + + # If either the start or stop index is negative, we'll need to cache + # the rest of the iterable in order to slice from the right side. + if (start < 0) or (stop < 0): + self._cache.extend(self._it) + # Otherwise we'll need to find the rightmost index and cache to that + # point. + else: + n = min(max(start, stop) + 1, maxsize) + cache_len = len(self._cache) + if n >= cache_len: + self._cache.extend(islice(self._it, n - cache_len)) + + return list(self._cache)[index] + + def __getitem__(self, index): + if isinstance(index, slice): + return self._get_slice(index) + + cache_len = len(self._cache) + if index < 0: + self._cache.extend(self._it) + elif index >= cache_len: + self._cache.extend(islice(self._it, index + 1 - cache_len)) + + return self._cache[index] + + +def consumer(func): + """Decorator that automatically advances a PEP-342-style "reverse iterator" + to its first yield point so you don't have to call ``next()`` on it + manually. + + >>> @consumer + ... def tally(): + ... i = 0 + ... while True: + ... print('Thing number %s is %s.' % (i, (yield))) + ... i += 1 + ... + >>> t = tally() + >>> t.send('red') + Thing number 0 is red. + >>> t.send('fish') + Thing number 1 is fish. + + Without the decorator, you would have to call ``next(t)`` before + ``t.send()`` could be used. + + """ + + @wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + next(gen) + return gen + + return wrapper + + +def ilen(iterable): + """Return the number of items in *iterable*. + + >>> ilen(x for x in range(1000000) if x % 3 == 0) + 333334 + + This consumes the iterable, so handle with care. + + """ + # This approach was selected because benchmarks showed it's likely the + # fastest of the known implementations at the time of writing. + # See GitHub tracker: #236, #230. + counter = count() + deque(zip(iterable, counter), maxlen=0) + return next(counter) + + +def iterate(func, start): + """Return ``start``, ``func(start)``, ``func(func(start))``, ... + + >>> from itertools import islice + >>> list(islice(iterate(lambda x: 2*x, 1), 10)) + [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + + """ + while True: + yield start + try: + start = func(start) + except StopIteration: + break + + +def with_iter(context_manager): + """Wrap an iterable in a ``with`` statement, so it closes once exhausted. + + For example, this will close the file when the iterator is exhausted:: + + upper_lines = (line.upper() for line in with_iter(open('foo'))) + + Any context manager which returns an iterable is a candidate for + ``with_iter``. + + """ + with context_manager as iterable: + yield from iterable + + +def one(iterable, too_short=None, too_long=None): + """Return the first item from *iterable*, which is expected to contain only + that item. Raise an exception if *iterable* is empty or has more than one + item. + + :func:`one` is useful for ensuring that an iterable contains only one item. + For example, it can be used to retrieve the result of a database query + that is expected to return a single row. + + If *iterable* is empty, ``ValueError`` will be raised. You may specify a + different exception with the *too_short* keyword: + + >>> it = [] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (expected 1)' + >>> too_short = IndexError('too few items') + >>> one(it, too_short=too_short) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + IndexError: too few items + + Similarly, if *iterable* contains more than one item, ``ValueError`` will + be raised. You may specify a different exception with the *too_long* + keyword: + + >>> it = ['too', 'many'] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 'too', + 'many', and perhaps more. + >>> too_long = RuntimeError + >>> one(it, too_long=too_long) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + Note that :func:`one` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check iterable + contents less destructively. + + """ + it = iter(iterable) + + try: + first_value = next(it) + except StopIteration as e: + raise ( + too_short or ValueError('too few items in iterable (expected 1)') + ) from e + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def raise_(exception, *args): + raise exception(*args) + + +def strictly_n(iterable, n, too_short=None, too_long=None): + """Validate that *iterable* has exactly *n* items and return them if + it does. If it has fewer than *n* items, call function *too_short* + with those items. If it has more than *n* items, call function + *too_long* with the first ``n + 1`` items. + + >>> iterable = ['a', 'b', 'c', 'd'] + >>> n = 4 + >>> list(strictly_n(iterable, n)) + ['a', 'b', 'c', 'd'] + + Note that the returned iterable must be consumed in order for the check to + be made. + + By default, *too_short* and *too_long* are functions that raise + ``ValueError``. + + >>> list(strictly_n('ab', 3)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too few items in iterable (got 2) + + >>> list(strictly_n('abc', 2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (got at least 3) + + You can instead supply functions that do something else. + *too_short* will be called with the number of items in *iterable*. + *too_long* will be called with `n + 1`. + + >>> def too_short(item_count): + ... raise RuntimeError + >>> it = strictly_n('abcd', 6, too_short=too_short) + >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + >>> def too_long(item_count): + ... print('The boss is going to hear about this') + >>> it = strictly_n('abcdef', 4, too_long=too_long) + >>> list(it) + The boss is going to hear about this + ['a', 'b', 'c', 'd'] + + """ + if too_short is None: + too_short = lambda item_count: raise_( + ValueError, + 'Too few items in iterable (got {})'.format(item_count), + ) + + if too_long is None: + too_long = lambda item_count: raise_( + ValueError, + 'Too many items in iterable (got at least {})'.format(item_count), + ) + + it = iter(iterable) + for i in range(n): + try: + item = next(it) + except StopIteration: + too_short(i) + return + else: + yield item + + try: + next(it) + except StopIteration: + pass + else: + too_long(n + 1) + + +def distinct_permutations(iterable, r=None): + """Yield successive distinct permutations of the elements in *iterable*. + + >>> sorted(distinct_permutations([1, 0, 1])) + [(0, 1, 1), (1, 0, 1), (1, 1, 0)] + + Equivalent to ``set(permutations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + Duplicate permutations arise when there are duplicated elements in the + input iterable. The number of items returned is + `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of + items input, and each `x_i` is the count of a distinct item in the input + sequence. + + If *r* is given, only the *r*-length permutations are yielded. + + >>> sorted(distinct_permutations([1, 0, 1], r=2)) + [(0, 1), (1, 0), (1, 1)] + >>> sorted(distinct_permutations(range(3), r=2)) + [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + + """ + + # Algorithm: https://w.wiki/Qai + def _full(A): + while True: + # Yield the permutation we have + yield tuple(A) + + # Find the largest index i such that A[i] < A[i + 1] + for i in range(size - 2, -1, -1): + if A[i] < A[i + 1]: + break + # If no such index exists, this permutation is the last one + else: + return + + # Find the largest index j greater than j such that A[i] < A[j] + for j in range(size - 1, i, -1): + if A[i] < A[j]: + break + + # Swap the value of A[i] with that of A[j], then reverse the + # sequence from A[i + 1] to form the new permutation + A[i], A[j] = A[j], A[i] + A[i + 1 :] = A[: i - size : -1] # A[i + 1:][::-1] + + # Algorithm: modified from the above + def _partial(A, r): + # Split A into the first r items and the last r items + head, tail = A[:r], A[r:] + right_head_indexes = range(r - 1, -1, -1) + left_tail_indexes = range(len(tail)) + + while True: + # Yield the permutation we have + yield tuple(head) + + # Starting from the right, find the first index of the head with + # value smaller than the maximum value of the tail - call it i. + pivot = tail[-1] + for i in right_head_indexes: + if head[i] < pivot: + break + pivot = head[i] + else: + return + + # Starting from the left, find the first value of the tail + # with a value greater than head[i] and swap. + for j in left_tail_indexes: + if tail[j] > head[i]: + head[i], tail[j] = tail[j], head[i] + break + # If we didn't find one, start from the right and find the first + # index of the head with a value greater than head[i] and swap. + else: + for j in right_head_indexes: + if head[j] > head[i]: + head[i], head[j] = head[j], head[i] + break + + # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] + tail += head[: i - r : -1] # head[i + 1:][::-1] + i += 1 + head[i:], tail[:] = tail[: r - i], tail[r - i :] + + items = sorted(iterable) + + size = len(items) + if r is None: + r = size + + if 0 < r <= size: + return _full(items) if (r == size) else _partial(items, r) + + return iter(() if r else ((),)) + + +def intersperse(e, iterable, n=1): + """Intersperse filler element *e* among the items in *iterable*, leaving + *n* items between each filler element. + + >>> list(intersperse('!', [1, 2, 3, 4, 5])) + [1, '!', 2, '!', 3, '!', 4, '!', 5] + + >>> list(intersperse(None, [1, 2, 3, 4, 5], n=2)) + [1, 2, None, 3, 4, None, 5] + + """ + if n == 0: + raise ValueError('n must be > 0') + elif n == 1: + # interleave(repeat(e), iterable) -> e, x_0, e, x_1, e, x_2... + # islice(..., 1, None) -> x_0, e, x_1, e, x_2... + return islice(interleave(repeat(e), iterable), 1, None) + else: + # interleave(filler, chunks) -> [e], [x_0, x_1], [e], [x_2, x_3]... + # islice(..., 1, None) -> [x_0, x_1], [e], [x_2, x_3]... + # flatten(...) -> x_0, x_1, e, x_2, x_3... + filler = repeat([e]) + chunks = chunked(iterable, n) + return flatten(islice(interleave(filler, chunks), 1, None)) + + +def unique_to_each(*iterables): + """Return the elements from each of the input iterables that aren't in the + other input iterables. + + For example, suppose you have a set of packages, each with a set of + dependencies:: + + {'pkg_1': {'A', 'B'}, 'pkg_2': {'B', 'C'}, 'pkg_3': {'B', 'D'}} + + If you remove one package, which dependencies can also be removed? + + If ``pkg_1`` is removed, then ``A`` is no longer necessary - it is not + associated with ``pkg_2`` or ``pkg_3``. Similarly, ``C`` is only needed for + ``pkg_2``, and ``D`` is only needed for ``pkg_3``:: + + >>> unique_to_each({'A', 'B'}, {'B', 'C'}, {'B', 'D'}) + [['A'], ['C'], ['D']] + + If there are duplicates in one input iterable that aren't in the others + they will be duplicated in the output. Input order is preserved:: + + >>> unique_to_each("mississippi", "missouri") + [['p', 'p'], ['o', 'u', 'r']] + + It is assumed that the elements of each iterable are hashable. + + """ + pool = [list(it) for it in iterables] + counts = Counter(chain.from_iterable(map(set, pool))) + uniques = {element for element in counts if counts[element] == 1} + return [list(filter(uniques.__contains__, it)) for it in pool] + + +def windowed(seq, n, fillvalue=None, step=1): + """Return a sliding window of width *n* over the given iterable. + + >>> all_windows = windowed([1, 2, 3, 4, 5], 3) + >>> list(all_windows) + [(1, 2, 3), (2, 3, 4), (3, 4, 5)] + + When the window is larger than the iterable, *fillvalue* is used in place + of missing values: + + >>> list(windowed([1, 2, 3], 4)) + [(1, 2, 3, None)] + + Each window will advance in increments of *step*: + + >>> list(windowed([1, 2, 3, 4, 5, 6], 3, fillvalue='!', step=2)) + [(1, 2, 3), (3, 4, 5), (5, 6, '!')] + + To slide into the iterable's items, use :func:`chain` to add filler items + to the left: + + >>> iterable = [1, 2, 3, 4] + >>> n = 3 + >>> padding = [None] * (n - 1) + >>> list(windowed(chain(padding, iterable), 3)) + [(None, None, 1), (None, 1, 2), (1, 2, 3), (2, 3, 4)] + """ + if n < 0: + raise ValueError('n must be >= 0') + if n == 0: + yield tuple() + return + if step < 1: + raise ValueError('step must be >= 1') + + window = deque(maxlen=n) + i = n + for _ in map(window.append, seq): + i -= 1 + if not i: + i = step + yield tuple(window) + + size = len(window) + if size == 0: + return + elif size < n: + yield tuple(chain(window, repeat(fillvalue, n - size))) + elif 0 < i < min(step, n): + window += (fillvalue,) * i + yield tuple(window) + + +def substrings(iterable): + """Yield all of the substrings of *iterable*. + + >>> [''.join(s) for s in substrings('more')] + ['m', 'o', 'r', 'e', 'mo', 'or', 're', 'mor', 'ore', 'more'] + + Note that non-string iterables can also be subdivided. + + >>> list(substrings([0, 1, 2])) + [(0,), (1,), (2,), (0, 1), (1, 2), (0, 1, 2)] + + """ + # The length-1 substrings + seq = [] + for item in iter(iterable): + seq.append(item) + yield (item,) + seq = tuple(seq) + item_count = len(seq) + + # And the rest + for n in range(2, item_count + 1): + for i in range(item_count - n + 1): + yield seq[i : i + n] + + +def substrings_indexes(seq, reverse=False): + """Yield all substrings and their positions in *seq* + + The items yielded will be a tuple of the form ``(substr, i, j)``, where + ``substr == seq[i:j]``. + + This function only works for iterables that support slicing, such as + ``str`` objects. + + >>> for item in substrings_indexes('more'): + ... print(item) + ('m', 0, 1) + ('o', 1, 2) + ('r', 2, 3) + ('e', 3, 4) + ('mo', 0, 2) + ('or', 1, 3) + ('re', 2, 4) + ('mor', 0, 3) + ('ore', 1, 4) + ('more', 0, 4) + + Set *reverse* to ``True`` to yield the same items in the opposite order. + + + """ + r = range(1, len(seq) + 1) + if reverse: + r = reversed(r) + return ( + (seq[i : i + L], i, i + L) for L in r for i in range(len(seq) - L + 1) + ) + + +class bucket: + """Wrap *iterable* and return an object that buckets the iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) + + +def spy(iterable, n=1): + """Return a 2-tuple with a list containing the first *n* elements of + *iterable*, and an iterator with the same items as *iterable*. + This allows you to "look ahead" at the items in the iterable without + advancing it. + + There is one item in the list by default: + + >>> iterable = 'abcdefg' + >>> head, iterable = spy(iterable) + >>> head + ['a'] + >>> list(iterable) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + You may use unpacking to retrieve items instead of lists: + + >>> (head,), iterable = spy('abcdefg') + >>> head + 'a' + >>> (first, second), iterable = spy('abcdefg', 2) + >>> first + 'a' + >>> second + 'b' + + The number of items requested can be larger than the number of items in + the iterable: + + >>> iterable = [1, 2, 3, 4, 5] + >>> head, iterable = spy(iterable, 10) + >>> head + [1, 2, 3, 4, 5] + >>> list(iterable) + [1, 2, 3, 4, 5] + + """ + it = iter(iterable) + head = take(n, it) + + return head.copy(), chain(head, it) + + +def interleave(*iterables): + """Return a new iterable yielding from each iterable in turn, + until the shortest is exhausted. + + >>> list(interleave([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7] + + For a version that doesn't terminate after the shortest iterable is + exhausted, see :func:`interleave_longest`. + + """ + return chain.from_iterable(zip(*iterables)) + + +def interleave_longest(*iterables): + """Return a new iterable yielding from each iterable in turn, + skipping any that are exhausted. + + >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7, 3, 8] + + This function produces the same output as :func:`roundrobin`, but may + perform better for some inputs (in particular when the number of iterables + is large). + + """ + i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) + return (x for x in i if x is not _marker) + + +def interleave_evenly(iterables, lengths=None): + """ + Interleave multiple iterables so that their elements are evenly distributed + throughout the output sequence. + + >>> iterables = [1, 2, 3, 4, 5], ['a', 'b'] + >>> list(interleave_evenly(iterables)) + [1, 2, 'a', 3, 4, 'b', 5] + + >>> iterables = [[1, 2, 3], [4, 5], [6, 7, 8]] + >>> list(interleave_evenly(iterables)) + [1, 6, 4, 2, 7, 3, 8, 5] + + This function requires iterables of known length. Iterables without + ``__len__()`` can be used by manually specifying lengths with *lengths*: + + >>> from itertools import combinations, repeat + >>> iterables = [combinations(range(4), 2), ['a', 'b', 'c']] + >>> lengths = [4 * (4 - 1) // 2, 3] + >>> list(interleave_evenly(iterables, lengths=lengths)) + [(0, 1), (0, 2), 'a', (0, 3), (1, 2), 'b', (1, 3), (2, 3), 'c'] + + Based on Bresenham's algorithm. + """ + if lengths is None: + try: + lengths = [len(it) for it in iterables] + except TypeError: + raise ValueError( + 'Iterable lengths could not be determined automatically. ' + 'Specify them with the lengths keyword.' + ) + elif len(iterables) != len(lengths): + raise ValueError('Mismatching number of iterables and lengths.') + + dims = len(lengths) + + # sort iterables by length, descending + lengths_permute = sorted( + range(dims), key=lambda i: lengths[i], reverse=True + ) + lengths_desc = [lengths[i] for i in lengths_permute] + iters_desc = [iter(iterables[i]) for i in lengths_permute] + + # the longest iterable is the primary one (Bresenham: the longest + # distance along an axis) + delta_primary, deltas_secondary = lengths_desc[0], lengths_desc[1:] + iter_primary, iters_secondary = iters_desc[0], iters_desc[1:] + errors = [delta_primary // dims] * len(deltas_secondary) + + to_yield = sum(lengths) + while to_yield: + yield next(iter_primary) + to_yield -= 1 + # update errors for each secondary iterable + errors = [e - delta for e, delta in zip(errors, deltas_secondary)] + + # those iterables for which the error is negative are yielded + # ("diagonal step" in Bresenham) + for i, e in enumerate(errors): + if e < 0: + yield next(iters_secondary[i]) + to_yield -= 1 + errors[i] += delta_primary + + +def collapse(iterable, base_type=None, levels=None): + """Flatten an iterable with multiple levels of nesting (e.g., a list of + lists of tuples) into non-iterable types. + + >>> iterable = [(1, 2), ([3, 4], [[5], [6]])] + >>> list(collapse(iterable)) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and + will not be collapsed. + + To avoid collapsing other types, specify *base_type*: + + >>> iterable = ['ab', ('cd', 'ef'), ['gh', 'ij']] + >>> list(collapse(iterable, base_type=tuple)) + ['ab', ('cd', 'ef'), 'gh', 'ij'] + + Specify *levels* to stop flattening after a certain level: + + >>> iterable = [('a', ['b']), ('c', ['d'])] + >>> list(collapse(iterable)) # Fully flattened + ['a', 'b', 'c', 'd'] + >>> list(collapse(iterable, levels=1)) # Only one level flattened + ['a', ['b'], 'c', ['d']] + + """ + + def walk(node, level): + if ( + ((levels is not None) and (level > levels)) + or isinstance(node, (str, bytes)) + or ((base_type is not None) and isinstance(node, base_type)) + ): + yield node + return + + try: + tree = iter(node) + except TypeError: + yield node + return + else: + for child in tree: + yield from walk(child, level + 1) + + yield from walk(iterable, 0) + + +def side_effect(func, iterable, chunk_size=None, before=None, after=None): + """Invoke *func* on each item in *iterable* (or on each *chunk_size* group + of items) before yielding the item. + + `func` must be a function that takes a single argument. Its return value + will be discarded. + + *before* and *after* are optional functions that take no arguments. They + will be executed before iteration starts and after it ends, respectively. + + `side_effect` can be used for logging, updating progress bars, or anything + that is not functionally "pure." + + Emitting a status message: + + >>> from more_itertools import consume + >>> func = lambda item: print('Received {}'.format(item)) + >>> consume(side_effect(func, range(2))) + Received 0 + Received 1 + + Operating on chunks of items: + + >>> pair_sums = [] + >>> func = lambda chunk: pair_sums.append(sum(chunk)) + >>> list(side_effect(func, [0, 1, 2, 3, 4, 5], 2)) + [0, 1, 2, 3, 4, 5] + >>> list(pair_sums) + [1, 5, 9] + + Writing to a file-like object: + + >>> from io import StringIO + >>> from more_itertools import consume + >>> f = StringIO() + >>> func = lambda x: print(x, file=f) + >>> before = lambda: print(u'HEADER', file=f) + >>> after = f.close + >>> it = [u'a', u'b', u'c'] + >>> consume(side_effect(func, it, before=before, after=after)) + >>> f.closed + True + + """ + try: + if before is not None: + before() + + if chunk_size is None: + for item in iterable: + func(item) + yield item + else: + for chunk in chunked(iterable, chunk_size): + func(chunk) + yield from chunk + finally: + if after is not None: + after() + + +def sliced(seq, n, strict=False): + """Yield slices of length *n* from the sequence *seq*. + + >>> list(sliced((1, 2, 3, 4, 5, 6), 3)) + [(1, 2, 3), (4, 5, 6)] + + By the default, the last yielded slice will have fewer than *n* elements + if the length of *seq* is not divisible by *n*: + + >>> list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3)) + [(1, 2, 3), (4, 5, 6), (7, 8)] + + If the length of *seq* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + slice is yielded. + + This function will only work for iterables that support slicing. + For non-sliceable iterables, see :func:`chunked`. + + """ + iterator = takewhile(len, (seq[i : i + n] for i in count(0, n))) + if strict: + + def ret(): + for _slice in iterator: + if len(_slice) != n: + raise ValueError("seq is not divisible by n.") + yield _slice + + return iter(ret()) + else: + return iterator + + +def split_at(iterable, pred, maxsplit=-1, keep_separator=False): + """Yield lists of items from *iterable*, where each list is delimited by + an item where callable *pred* returns ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b')) + [['a'], ['c', 'd', 'c'], ['a']] + + >>> list(split_at(range(10), lambda n: n % 2 == 1)) + [[0], [2], [4], [6], [8], []] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_at(range(10), lambda n: n % 2 == 1, maxsplit=2)) + [[0], [2], [4, 5, 6, 7, 8, 9]] + + By default, the delimiting items are not included in the output. + To include them, set *keep_separator* to ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b', keep_separator=True)) + [['a'], ['b'], ['c', 'd', 'c'], ['b'], ['a']] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item): + yield buf + if keep_separator: + yield [item] + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + else: + buf.append(item) + yield buf + + +def split_before(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends just before + an item for which callable *pred* returns ``True``: + + >>> list(split_before('OneTwo', lambda s: s.isupper())) + [['O', 'n', 'e'], ['T', 'w', 'o']] + + >>> list(split_before(range(10), lambda n: n % 3 == 0)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_before(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]] + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield [item] + list(it) + return + buf = [] + maxsplit -= 1 + buf.append(item) + if buf: + yield buf + + +def split_after(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends with an + item where callable *pred* returns ``True``: + + >>> list(split_after('one1two2', lambda s: s.isdigit())) + [['o', 'n', 'e', '1'], ['t', 'w', 'o', '2']] + + >>> list(split_after(range(10), lambda n: n % 3 == 0)) + [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_after(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0], [1, 2, 3], [4, 5, 6, 7, 8, 9]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + buf.append(item) + if pred(item) and buf: + yield buf + if maxsplit == 1: + buf = list(it) + if buf: + yield buf + return + buf = [] + maxsplit -= 1 + if buf: + yield buf + + +def split_when(iterable, pred, maxsplit=-1): + """Split *iterable* into pieces based on the output of *pred*. + *pred* should be a function that takes successive pairs of items and + returns ``True`` if the iterable should be split in between them. + + For example, to find runs of increasing numbers, split the iterable when + element ``i`` is larger than element ``i + 1``: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], lambda x, y: x > y)) + [[1, 2, 3, 3], [2, 5], [2, 4], [2]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], + ... lambda x, y: x > y, maxsplit=2)) + [[1, 2, 3, 3], [2, 5], [2, 4, 2]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + it = iter(iterable) + try: + cur_item = next(it) + except StopIteration: + return + + buf = [cur_item] + for next_item in it: + if pred(cur_item, next_item): + yield buf + if maxsplit == 1: + yield [next_item] + list(it) + return + buf = [] + maxsplit -= 1 + + buf.append(next_item) + cur_item = next_item + + yield buf + + +def split_into(iterable, sizes): + """Yield a list of sequential items from *iterable* of length 'n' for each + integer 'n' in *sizes*. + + >>> list(split_into([1,2,3,4,5,6], [1,2,3])) + [[1], [2, 3], [4, 5, 6]] + + If the sum of *sizes* is smaller than the length of *iterable*, then the + remaining items of *iterable* will not be returned. + + >>> list(split_into([1,2,3,4,5,6], [2,3])) + [[1, 2], [3, 4, 5]] + + If the sum of *sizes* is larger than the length of *iterable*, fewer items + will be returned in the iteration that overruns *iterable* and further + lists will be empty: + + >>> list(split_into([1,2,3,4], [1,2,3,4])) + [[1], [2, 3], [4], []] + + When a ``None`` object is encountered in *sizes*, the returned list will + contain items up to the end of *iterable* the same way that itertools.slice + does: + + >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None])) + [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]] + + :func:`split_into` can be useful for grouping a series of items where the + sizes of the groups are not uniform. An example would be where in a row + from a table, multiple columns represent elements of the same feature + (e.g. a point represented by x,y,z) but, the format is not the same for + all columns. + """ + # convert the iterable argument into an iterator so its contents can + # be consumed by islice in case it is a generator + it = iter(iterable) + + for size in sizes: + if size is None: + yield list(it) + return + else: + yield list(islice(it, size)) + + +def padded(iterable, fillvalue=None, n=None, next_multiple=False): + """Yield the elements from *iterable*, followed by *fillvalue*, such that + at least *n* items are emitted. + + >>> list(padded([1, 2, 3], '?', 5)) + [1, 2, 3, '?', '?'] + + If *next_multiple* is ``True``, *fillvalue* will be emitted until the + number of items emitted is a multiple of *n*:: + + >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) + [1, 2, 3, 4, None, None] + + If *n* is ``None``, *fillvalue* will be emitted indefinitely. + + """ + it = iter(iterable) + if n is None: + yield from chain(it, repeat(fillvalue)) + elif n < 1: + raise ValueError('n must be at least 1') + else: + item_count = 0 + for item in it: + yield item + item_count += 1 + + remaining = (n - item_count) % n if next_multiple else n - item_count + for _ in range(remaining): + yield fillvalue + + +def repeat_each(iterable, n=2): + """Repeat each element in *iterable* *n* times. + + >>> list(repeat_each('ABC', 3)) + ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'] + """ + return chain.from_iterable(map(repeat, iterable, repeat(n))) + + +def repeat_last(iterable, default=None): + """After the *iterable* is exhausted, keep yielding its last element. + + >>> list(islice(repeat_last(range(3)), 5)) + [0, 1, 2, 2, 2] + + If the iterable is empty, yield *default* forever:: + + >>> list(islice(repeat_last(range(0), 42), 5)) + [42, 42, 42, 42, 42] + + """ + item = _marker + for item in iterable: + yield item + final = default if item is _marker else item + yield from repeat(final) + + +def distribute(n, iterable): + """Distribute the items from *iterable* among *n* smaller iterables. + + >>> group_1, group_2 = distribute(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 3, 5] + >>> list(group_2) + [2, 4, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = distribute(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 4, 7], [2, 5], [3, 6]] + + If the length of *iterable* is smaller than *n*, then the last returned + iterables will be empty: + + >>> children = distribute(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function uses :func:`itertools.tee` and may require significant + storage. If you need the order items in the smaller iterables to match the + original iterable, see :func:`divide`. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + children = tee(iterable, n) + return [islice(it, index, None, n) for index, it in enumerate(children)] + + +def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None): + """Yield tuples whose elements are offset from *iterable*. + The amount by which the `i`-th item in each tuple is offset is given by + the `i`-th item in *offsets*. + + >>> list(stagger([0, 1, 2, 3])) + [(None, 0, 1), (0, 1, 2), (1, 2, 3)] + >>> list(stagger(range(8), offsets=(0, 2, 4))) + [(0, 2, 4), (1, 3, 5), (2, 4, 6), (3, 5, 7)] + + By default, the sequence will end when the final element of a tuple is the + last item in the iterable. To continue until the first element of a tuple + is the last item in the iterable, set *longest* to ``True``:: + + >>> list(stagger([0, 1, 2, 3], longest=True)) + [(None, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, None), (3, None, None)] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + children = tee(iterable, len(offsets)) + + return zip_offset( + *children, offsets=offsets, longest=longest, fillvalue=fillvalue + ) + + +def zip_equal(*iterables): + """``zip`` the input *iterables* together, but raise + ``UnequalIterablesError`` if they aren't all the same length. + + >>> it_1 = range(3) + >>> it_2 = iter('abc') + >>> list(zip_equal(it_1, it_2)) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> it_1 = range(3) + >>> it_2 = iter('abcd') + >>> list(zip_equal(it_1, it_2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + more_itertools.more.UnequalIterablesError: Iterables have different + lengths + + """ + if hexversion >= 0x30A00A6: + warnings.warn( + ( + 'zip_equal will be removed in a future version of ' + 'more-itertools. Use the builtin zip function with ' + 'strict=True instead.' + ), + DeprecationWarning, + ) + + return _zip_equal(*iterables) + + +def zip_offset(*iterables, offsets, longest=False, fillvalue=None): + """``zip`` the input *iterables* together, but offset the `i`-th iterable + by the `i`-th item in *offsets*. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1))) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')] + + This can be used as a lightweight alternative to SciPy or pandas to analyze + data sets in which some series have a lead or lag relationship. + + By default, the sequence will end when the shortest iterable is exhausted. + To continue until the longest iterable is exhausted, set *longest* to + ``True``. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1), longest=True)) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + if len(iterables) != len(offsets): + raise ValueError("Number of iterables and offsets didn't match") + + staggered = [] + for it, n in zip(iterables, offsets): + if n < 0: + staggered.append(chain(repeat(fillvalue, -n), it)) + elif n > 0: + staggered.append(islice(it, n, None)) + else: + staggered.append(it) + + if longest: + return zip_longest(*staggered, fillvalue=fillvalue) + + return zip(*staggered) + + +def sort_together(iterables, key_list=(0,), key=None, reverse=False): + """Return the input iterables sorted together, with *key_list* as the + priority for sorting. All iterables are trimmed to the length of the + shortest one. + + This can be used like the sorting function in a spreadsheet. If each + iterable represents a column of data, the key list determines which + columns are used for sorting. + + By default, all iterables are sorted using the ``0``-th iterable:: + + >>> iterables = [(4, 3, 2, 1), ('a', 'b', 'c', 'd')] + >>> sort_together(iterables) + [(1, 2, 3, 4), ('d', 'c', 'b', 'a')] + + Set a different key list to sort according to another iterable. + Specifying multiple keys dictates how ties are broken:: + + >>> iterables = [(3, 1, 2), (0, 1, 0), ('c', 'b', 'a')] + >>> sort_together(iterables, key_list=(1, 2)) + [(2, 3, 1), (0, 0, 1), ('a', 'c', 'b')] + + To sort by a function of the elements of the iterable, pass a *key* + function. Its arguments are the elements of the iterables corresponding to + the key list:: + + >>> names = ('a', 'b', 'c') + >>> lengths = (1, 2, 3) + >>> widths = (5, 2, 1) + >>> def area(length, width): + ... return length * width + >>> sort_together([names, lengths, widths], key_list=(1, 2), key=area) + [('c', 'b', 'a'), (3, 2, 1), (1, 2, 5)] + + Set *reverse* to ``True`` to sort in descending order. + + >>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True) + [(3, 2, 1), ('a', 'b', 'c')] + + """ + if key is None: + # if there is no key function, the key argument to sorted is an + # itemgetter + key_argument = itemgetter(*key_list) + else: + # if there is a key function, call it with the items at the offsets + # specified by the key function as arguments + key_list = list(key_list) + if len(key_list) == 1: + # if key_list contains a single item, pass the item at that offset + # as the only argument to the key function + key_offset = key_list[0] + key_argument = lambda zipped_items: key(zipped_items[key_offset]) + else: + # if key_list contains multiple items, use itemgetter to return a + # tuple of items, which we pass as *args to the key function + get_key_items = itemgetter(*key_list) + key_argument = lambda zipped_items: key( + *get_key_items(zipped_items) + ) + + return list( + zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse)) + ) + + +def unzip(iterable): + """The inverse of :func:`zip`, this function disaggregates the elements + of the zipped *iterable*. + + The ``i``-th iterable contains the ``i``-th element from each element + of the zipped iterable. The first element is used to determine the + length of the remaining elements. + + >>> iterable = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> letters, numbers = unzip(iterable) + >>> list(letters) + ['a', 'b', 'c', 'd'] + >>> list(numbers) + [1, 2, 3, 4] + + This is similar to using ``zip(*iterable)``, but it avoids reading + *iterable* into memory. Note, however, that this function uses + :func:`itertools.tee` and thus may require significant storage. + + """ + head, iterable = spy(iter(iterable)) + if not head: + # empty iterable, e.g. zip([], [], []) + return () + # spy returns a one-length iterable as head + head = head[0] + iterables = tee(iterable, len(head)) + + def itemgetter(i): + def getter(obj): + try: + return obj[i] + except IndexError: + # basically if we have an iterable like + # iter([(1, 2, 3), (4, 5), (6,)]) + # the second unzipped iterable would fail at the third tuple + # since it would try to access tup[1] + # same with the third unzipped iterable and the second tuple + # to support these "improperly zipped" iterables, + # we create a custom itemgetter + # which just stops the unzipped iterables + # at first length mismatch + raise StopIteration + + return getter + + return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables)) + + +def divide(n, iterable): + """Divide the elements from *iterable* into *n* parts, maintaining + order. + + >>> group_1, group_2 = divide(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 2, 3] + >>> list(group_2) + [4, 5, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = divide(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 2, 3], [4, 5], [6, 7]] + + If the length of the iterable is smaller than n, then the last returned + iterables will be empty: + + >>> children = divide(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function will exhaust the iterable before returning and may require + significant storage. If order is not important, see :func:`distribute`, + which does not first pull the iterable into memory. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + try: + iterable[:0] + except TypeError: + seq = tuple(iterable) + else: + seq = iterable + + q, r = divmod(len(seq), n) + + ret = [] + stop = 0 + for i in range(1, n + 1): + start = stop + stop += q + 1 if i <= r else q + ret.append(iter(seq[start:stop])) + + return ret + + +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +def adjacent(predicate, iterable, distance=1): + """Return an iterable over `(bool, item)` tuples where the `item` is + drawn from *iterable* and the `bool` indicates whether + that item satisfies the *predicate* or is adjacent to an item that does. + + For example, to find whether items are adjacent to a ``3``:: + + >>> list(adjacent(lambda x: x == 3, range(6))) + [(False, 0), (False, 1), (True, 2), (True, 3), (True, 4), (False, 5)] + + Set *distance* to change what counts as adjacent. For example, to find + whether items are two places away from a ``3``: + + >>> list(adjacent(lambda x: x == 3, range(6), distance=2)) + [(False, 0), (True, 1), (True, 2), (True, 3), (True, 4), (True, 5)] + + This is useful for contextualizing the results of a search function. + For example, a code comparison tool might want to identify lines that + have changed, but also surrounding lines to give the viewer of the diff + context. + + The predicate function will only be called once for each item in the + iterable. + + See also :func:`groupby_transform`, which can be used with this function + to group ranges of items with the same `bool` value. + + """ + # Allow distance=0 mainly for testing that it reproduces results with map() + if distance < 0: + raise ValueError('distance must be at least 0') + + i1, i2 = tee(iterable) + padding = [False] * distance + selected = chain(padding, map(predicate, i1), padding) + adjacent_to_selected = map(any, windowed(selected, 2 * distance + 1)) + return zip(adjacent_to_selected, i2) + + +def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None): + """An extension of :func:`itertools.groupby` that can apply transformations + to the grouped data. + + * *keyfunc* is a function computing a key value for each item in *iterable* + * *valuefunc* is a function that transforms the individual items from + *iterable* after grouping + * *reducefunc* is a function that transforms each group of items + + >>> iterable = 'aAAbBBcCC' + >>> keyfunc = lambda k: k.upper() + >>> valuefunc = lambda v: v.lower() + >>> reducefunc = lambda g: ''.join(g) + >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc)) + [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')] + + Each optional argument defaults to an identity function if not specified. + + :func:`groupby_transform` is useful when grouping elements of an iterable + using a separate iterable as the key. To do this, :func:`zip` the iterables + and pass a *keyfunc* that extracts the first element and a *valuefunc* + that extracts the second element:: + + >>> from operator import itemgetter + >>> keys = [0, 0, 1, 1, 1, 2, 2, 2, 3] + >>> values = 'abcdefghi' + >>> iterable = zip(keys, values) + >>> grouper = groupby_transform(iterable, itemgetter(0), itemgetter(1)) + >>> [(k, ''.join(g)) for k, g in grouper] + [(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')] + + Note that the order of items in the iterable is significant. + Only adjacent items are grouped together, so if you don't want any + duplicate groups, you should sort the iterable by the key function. + + """ + ret = groupby(iterable, keyfunc) + if valuefunc: + ret = ((k, map(valuefunc, g)) for k, g in ret) + if reducefunc: + ret = ((k, reducefunc(g)) for k, g in ret) + + return ret + + +class numeric_range(abc.Sequence, abc.Hashable): + """An extension of the built-in ``range()`` function whose arguments can + be any orderable numeric type. + + With only *stop* specified, *start* defaults to ``0`` and *step* + defaults to ``1``. The output items will match the type of *stop*: + + >>> list(numeric_range(3.5)) + [0.0, 1.0, 2.0, 3.0] + + With only *start* and *stop* specified, *step* defaults to ``1``. The + output items will match the type of *start*: + + >>> from decimal import Decimal + >>> start = Decimal('2.1') + >>> stop = Decimal('5.1') + >>> list(numeric_range(start, stop)) + [Decimal('2.1'), Decimal('3.1'), Decimal('4.1')] + + With *start*, *stop*, and *step* specified the output items will match + the type of ``start + step``: + + >>> from fractions import Fraction + >>> start = Fraction(1, 2) # Start at 1/2 + >>> stop = Fraction(5, 2) # End at 5/2 + >>> step = Fraction(1, 2) # Count by 1/2 + >>> list(numeric_range(start, stop, step)) + [Fraction(1, 2), Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)] + + If *step* is zero, ``ValueError`` is raised. Negative steps are supported: + + >>> list(numeric_range(3, -1, -1.0)) + [3.0, 2.0, 1.0, 0.0] + + Be aware of the limitations of floating point numbers; the representation + of the yielded numbers may be surprising. + + ``datetime.datetime`` objects can be used for *start* and *stop*, if *step* + is a ``datetime.timedelta`` object: + + >>> import datetime + >>> start = datetime.datetime(2019, 1, 1) + >>> stop = datetime.datetime(2019, 1, 3) + >>> step = datetime.timedelta(days=1) + >>> items = iter(numeric_range(start, stop, step)) + >>> next(items) + datetime.datetime(2019, 1, 1, 0, 0) + >>> next(items) + datetime.datetime(2019, 1, 2, 0, 0) + + """ + + _EMPTY_HASH = hash(range(0, 0)) + + def __init__(self, *args): + argc = len(args) + if argc == 1: + (self._stop,) = args + self._start = type(self._stop)(0) + self._step = type(self._stop - self._start)(1) + elif argc == 2: + self._start, self._stop = args + self._step = type(self._stop - self._start)(1) + elif argc == 3: + self._start, self._stop, self._step = args + elif argc == 0: + raise TypeError( + 'numeric_range expected at least ' + '1 argument, got {}'.format(argc) + ) + else: + raise TypeError( + 'numeric_range expected at most ' + '3 arguments, got {}'.format(argc) + ) + + self._zero = type(self._step)(0) + if self._step == self._zero: + raise ValueError('numeric_range() arg 3 must not be zero') + self._growing = self._step > self._zero + + def __bool__(self): + if self._growing: + return self._start < self._stop + else: + return self._start > self._stop + + def __contains__(self, elem): + if self._growing: + if self._start <= elem < self._stop: + return (elem - self._start) % self._step == self._zero + else: + if self._start >= elem > self._stop: + return (self._start - elem) % (-self._step) == self._zero + + return False + + def __eq__(self, other): + if isinstance(other, numeric_range): + empty_self = not bool(self) + empty_other = not bool(other) + if empty_self or empty_other: + return empty_self and empty_other # True if both empty + else: + return ( + self._start == other._start + and self._step == other._step + and self._get_by_index(-1) == other._get_by_index(-1) + ) + else: + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self._get_by_index(key) + elif isinstance(key, slice): + step = self._step if key.step is None else key.step * self._step + + if key.start is None or key.start <= -self._len: + start = self._start + elif key.start >= self._len: + start = self._stop + else: # -self._len < key.start < self._len + start = self._get_by_index(key.start) + + if key.stop is None or key.stop >= self._len: + stop = self._stop + elif key.stop <= -self._len: + stop = self._start + else: # -self._len < key.stop < self._len + stop = self._get_by_index(key.stop) + + return numeric_range(start, stop, step) + else: + raise TypeError( + 'numeric range indices must be ' + 'integers or slices, not {}'.format(type(key).__name__) + ) + + def __hash__(self): + if self: + return hash((self._start, self._get_by_index(-1), self._step)) + else: + return self._EMPTY_HASH + + def __iter__(self): + values = (self._start + (n * self._step) for n in count()) + if self._growing: + return takewhile(partial(gt, self._stop), values) + else: + return takewhile(partial(lt, self._stop), values) + + def __len__(self): + return self._len + + @cached_property + def _len(self): + if self._growing: + start = self._start + stop = self._stop + step = self._step + else: + start = self._stop + stop = self._start + step = -self._step + distance = stop - start + if distance <= self._zero: + return 0 + else: # distance > 0 and step > 0: regular euclidean division + q, r = divmod(distance, step) + return int(q) + int(r != self._zero) + + def __reduce__(self): + return numeric_range, (self._start, self._stop, self._step) + + def __repr__(self): + if self._step == 1: + return "numeric_range({}, {})".format( + repr(self._start), repr(self._stop) + ) + else: + return "numeric_range({}, {}, {})".format( + repr(self._start), repr(self._stop), repr(self._step) + ) + + def __reversed__(self): + return iter( + numeric_range( + self._get_by_index(-1), self._start - self._step, -self._step + ) + ) + + def count(self, value): + return int(value in self) + + def index(self, value): + if self._growing: + if self._start <= value < self._stop: + q, r = divmod(value - self._start, self._step) + if r == self._zero: + return int(q) + else: + if self._start >= value > self._stop: + q, r = divmod(self._start - value, -self._step) + if r == self._zero: + return int(q) + + raise ValueError("{} is not in numeric range".format(value)) + + def _get_by_index(self, i): + if i < 0: + i += self._len + if i < 0 or i >= self._len: + raise IndexError("numeric range object index out of range") + return self._start + i * self._step + + +def count_cycle(iterable, n=None): + """Cycle through the items from *iterable* up to *n* times, yielding + the number of completed cycles along with each item. If *n* is omitted the + process repeats indefinitely. + + >>> list(count_cycle('AB', 3)) + [(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')] + + """ + iterable = tuple(iterable) + if not iterable: + return iter(()) + counter = count() if n is None else range(n) + return ((i, item) for i in counter for item in iterable) + + +def mark_ends(iterable): + """Yield 3-tuples of the form ``(is_first, is_last, item)``. + + >>> list(mark_ends('ABC')) + [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')] + + Use this when looping over an iterable to take special action on its first + and/or last items: + + >>> iterable = ['Header', 100, 200, 'Footer'] + >>> total = 0 + >>> for is_first, is_last, item in mark_ends(iterable): + ... if is_first: + ... continue # Skip the header + ... if is_last: + ... continue # Skip the footer + ... total += item + >>> print(total) + 300 + """ + it = iter(iterable) + + try: + b = next(it) + except StopIteration: + return + + try: + for i in count(): + a = b + b = next(it) + yield i == 0, False, a + + except StopIteration: + yield i == 0, True, a + + +def locate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(locate([0, 1, 1, 0, 1, 0, 0])) + [1, 2, 4] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item. + + >>> list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b')) + [1, 3] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(locate(iterable, pred=pred, window_size=3)) + [1, 5, 9] + + Use with :func:`seekable` to find indexes and then retrieve the associated + items: + + >>> from itertools import count + >>> from more_itertools import seekable + >>> source = (3 * n + 1 if (n % 2) else n // 2 for n in count()) + >>> it = seekable(source) + >>> pred = lambda x: x > 100 + >>> indexes = locate(it, pred=pred) + >>> i = next(indexes) + >>> it.seek(i) + >>> next(it) + 106 + + """ + if window_size is None: + return compress(count(), map(pred, iterable)) + + if window_size < 1: + raise ValueError('window size must be at least 1') + + it = windowed(iterable, window_size, fillvalue=_marker) + return compress(count(), starmap(pred, it)) + + +def longest_common_prefix(iterables): + """Yield elements of the longest common prefix amongst given *iterables*. + + >>> ''.join(longest_common_prefix(['abcd', 'abc', 'abf'])) + 'ab' + + """ + return (c[0] for c in takewhile(all_equal, zip(*iterables))) + + +def lstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the beginning + for which *pred* returns ``True``. + + For example, to remove a set of items from the start of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(lstrip(iterable, pred)) + [1, 2, None, 3, False, None] + + This function is analogous to to :func:`str.lstrip`, and is essentially + an wrapper for :func:`itertools.dropwhile`. + + """ + return dropwhile(pred, iterable) + + +def rstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the end + for which *pred* returns ``True``. + + For example, to remove a set of items from the end of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(rstrip(iterable, pred)) + [None, False, None, 1, 2, None, 3] + + This function is analogous to :func:`str.rstrip`. + + """ + cache = [] + cache_append = cache.append + cache_clear = cache.clear + for x in iterable: + if pred(x): + cache_append(x) + else: + yield from cache + cache_clear() + yield x + + +def strip(iterable, pred): + """Yield the items from *iterable*, but strip any from the + beginning and end for which *pred* returns ``True``. + + For example, to remove a set of items from both ends of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(strip(iterable, pred)) + [1, 2, None, 3] + + This function is analogous to :func:`str.strip`. + + """ + return rstrip(lstrip(iterable, pred), pred) + + +class islice_extended: + """An extension of :func:`itertools.islice` that supports negative values + for *stop*, *start*, and *step*. + + >>> iterable = iter('abcdefgh') + >>> list(islice_extended(iterable, -4, -1)) + ['e', 'f', 'g'] + + Slices with negative values require some caching of *iterable*, but this + function takes care to minimize the amount of memory required. + + For example, you can use a negative step with an infinite iterator: + + >>> from itertools import count + >>> list(islice_extended(count(), 110, 99, -2)) + [110, 108, 106, 104, 102, 100] + + You can also use slice notation directly: + + >>> iterable = map(str, count()) + >>> it = islice_extended(iterable)[10:20:2] + >>> list(it) + ['10', '12', '14', '16', '18'] + + """ + + def __init__(self, iterable, *args): + it = iter(iterable) + if args: + self._iterable = _islice_helper(it, slice(*args)) + else: + self._iterable = it + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterable) + + def __getitem__(self, key): + if isinstance(key, slice): + return islice_extended(_islice_helper(self._iterable, key)) + + raise TypeError('islice_extended.__getitem__ argument must be a slice') + + +def _islice_helper(it, s): + start = s.start + stop = s.stop + if s.step == 0: + raise ValueError('step argument must be a non-zero integer or None.') + step = s.step or 1 + + if step > 0: + start = 0 if (start is None) else start + + if start < 0: + # Consume all but the last -start items + cache = deque(enumerate(it, 1), maxlen=-start) + len_iter = cache[-1][0] if cache else 0 + + # Adjust start to be positive + i = max(len_iter + start, 0) + + # Adjust stop to be positive + if stop is None: + j = len_iter + elif stop >= 0: + j = min(stop, len_iter) + else: + j = max(len_iter + stop, 0) + + # Slice the cache + n = j - i + if n <= 0: + return + + for index, item in islice(cache, 0, n, step): + yield item + elif (stop is not None) and (stop < 0): + # Advance to the start position + next(islice(it, start, start), None) + + # When stop is negative, we have to carry -stop items while + # iterating + cache = deque(islice(it, -stop), maxlen=-stop) + + for index, item in enumerate(it): + cached_item = cache.popleft() + if index % step == 0: + yield cached_item + cache.append(item) + else: + # When both start and stop are positive we have the normal case + yield from islice(it, start, stop, step) + else: + start = -1 if (start is None) else start + + if (stop is not None) and (stop < 0): + # Consume all but the last items + n = -stop - 1 + cache = deque(enumerate(it, 1), maxlen=n) + len_iter = cache[-1][0] if cache else 0 + + # If start and stop are both negative they are comparable and + # we can just slice. Otherwise we can adjust start to be negative + # and then slice. + if start < 0: + i, j = start, stop + else: + i, j = min(start - len_iter, -1), None + + for index, item in list(cache)[i:j:step]: + yield item + else: + # Advance to the stop position + if stop is not None: + m = stop + 1 + next(islice(it, m, m), None) + + # stop is positive, so if start is negative they are not comparable + # and we need the rest of the items. + if start < 0: + i = start + n = None + # stop is None and start is positive, so we just need items up to + # the start index. + elif stop is None: + i = None + n = start + 1 + # Both stop and start are positive, so they are comparable. + else: + i = None + n = start - stop + if n <= 0: + return + + cache = list(islice(it, n)) + + yield from cache[i::step] + + +def always_reversible(iterable): + """An extension of :func:`reversed` that supports all iterables, not + just those which implement the ``Reversible`` or ``Sequence`` protocols. + + >>> print(*always_reversible(x for x in range(3))) + 2 1 0 + + If the iterable is already reversible, this function returns the + result of :func:`reversed()`. If the iterable is not reversible, + this function will cache the remaining items in the iterable and + yield them in reverse order, which may require significant storage. + """ + try: + return reversed(iterable) + except TypeError: + return reversed(list(iterable)) + + +def consecutive_groups(iterable, ordering=lambda x: x): + """Yield groups of consecutive items using :func:`itertools.groupby`. + The *ordering* function determines whether two items are adjacent by + returning their position. + + By default, the ordering function is the identity function. This is + suitable for finding runs of numbers: + + >>> iterable = [1, 10, 11, 12, 20, 30, 31, 32, 33, 40] + >>> for group in consecutive_groups(iterable): + ... print(list(group)) + [1] + [10, 11, 12] + [20] + [30, 31, 32, 33] + [40] + + For finding runs of adjacent letters, try using the :meth:`index` method + of a string of letters: + + >>> from string import ascii_lowercase + >>> iterable = 'abcdfgilmnop' + >>> ordering = ascii_lowercase.index + >>> for group in consecutive_groups(iterable, ordering): + ... print(list(group)) + ['a', 'b', 'c', 'd'] + ['f', 'g'] + ['i'] + ['l', 'm', 'n', 'o', 'p'] + + Each group of consecutive items is an iterator that shares it source with + *iterable*. When an an output group is advanced, the previous group is + no longer available unless its elements are copied (e.g., into a ``list``). + + >>> iterable = [1, 2, 11, 12, 21, 22] + >>> saved_groups = [] + >>> for group in consecutive_groups(iterable): + ... saved_groups.append(list(group)) # Copy group elements + >>> saved_groups + [[1, 2], [11, 12], [21, 22]] + + """ + for k, g in groupby( + enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) + ): + yield map(itemgetter(1), g) + + +def difference(iterable, func=sub, *, initial=None): + """This function is the inverse of :func:`itertools.accumulate`. By default + it will compute the first difference of *iterable* using + :func:`operator.sub`: + + >>> from itertools import accumulate + >>> iterable = accumulate([0, 1, 2, 3, 4]) # produces 0, 1, 3, 6, 10 + >>> list(difference(iterable)) + [0, 1, 2, 3, 4] + + *func* defaults to :func:`operator.sub`, but other functions can be + specified. They will be applied as follows:: + + A, B, C, D, ... --> A, func(B, A), func(C, B), func(D, C), ... + + For example, to do progressive division: + + >>> iterable = [1, 2, 6, 24, 120] + >>> func = lambda x, y: x // y + >>> list(difference(iterable, func)) + [1, 2, 3, 4, 5] + + If the *initial* keyword is set, the first element will be skipped when + computing successive differences. + + >>> it = [10, 11, 13, 16] # from accumulate([1, 2, 3], initial=10) + >>> list(difference(it, initial=10)) + [1, 2, 3] + + """ + a, b = tee(iterable) + try: + first = [next(b)] + except StopIteration: + return iter([]) + + if initial is not None: + first = [] + + return chain(first, map(func, b, a)) + + +class SequenceView(Sequence): + """Return a read-only view of the sequence object *target*. + + :class:`SequenceView` objects are analogous to Python's built-in + "dictionary view" types. They provide a dynamic view of a sequence's items, + meaning that when the sequence updates, so does the view. + + >>> seq = ['0', '1', '2'] + >>> view = SequenceView(seq) + >>> view + SequenceView(['0', '1', '2']) + >>> seq.append('3') + >>> view + SequenceView(['0', '1', '2', '3']) + + Sequence views support indexing, slicing, and length queries. They act + like the underlying sequence, except they don't allow assignment: + + >>> view[1] + '1' + >>> view[1:-1] + ['1', '2'] + >>> len(view) + 4 + + Sequence views are useful as an alternative to copying, as they don't + require (much) extra storage. + + """ + + def __init__(self, target): + if not isinstance(target, Sequence): + raise TypeError + self._target = target + + def __getitem__(self, index): + return self._target[index] + + def __len__(self): + return len(self._target) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, repr(self._target)) + + +class seekable: + """Wrap an iterator to allow for seeking backward and forward. This + progressively caches the items in the source iterable so they can be + re-visited. + + Call :meth:`seek` with an index to seek to that position in the source + iterable. + + To "reset" an iterator, seek to ``0``: + + >>> from itertools import count + >>> it = seekable((str(n) for n in count())) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> it.seek(0) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> next(it) + '3' + + You can also seek forward: + + >>> it = seekable((str(n) for n in range(20))) + >>> it.seek(10) + >>> next(it) + '10' + >>> it.relative_seek(-2) # Seeking relative to the current position + >>> next(it) + '9' + >>> it.seek(20) # Seeking past the end of the source isn't a problem + >>> list(it) + [] + >>> it.seek(0) # Resetting works even after hitting the end + >>> next(it), next(it), next(it) + ('0', '1', '2') + + Call :meth:`peek` to look ahead one item without advancing the iterator: + + >>> it = seekable('1234') + >>> it.peek() + '1' + >>> list(it) + ['1', '2', '3', '4'] + >>> it.peek(default='empty') + 'empty' + + Before the iterator is at its end, calling :func:`bool` on it will return + ``True``. After it will return ``False``: + + >>> it = seekable('5678') + >>> bool(it) + True + >>> list(it) + ['5', '6', '7', '8'] + >>> bool(it) + False + + You may view the contents of the cache with the :meth:`elements` method. + That returns a :class:`SequenceView`, a view that updates automatically: + + >>> it = seekable((str(n) for n in range(10))) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> elements = it.elements() + >>> elements + SequenceView(['0', '1', '2']) + >>> next(it) + '3' + >>> elements + SequenceView(['0', '1', '2', '3']) + + By default, the cache grows as the source iterable progresses, so beware of + wrapping very large or infinite iterables. Supply *maxlen* to limit the + size of the cache (this of course limits how far back you can seek). + + >>> from itertools import count + >>> it = seekable((str(n) for n in count()), maxlen=2) + >>> next(it), next(it), next(it), next(it) + ('0', '1', '2', '3') + >>> list(it.elements()) + ['2', '3'] + >>> it.seek(0) + >>> next(it), next(it), next(it), next(it) + ('2', '3', '4', '5') + >>> next(it) + '6' + + """ + + def __init__(self, iterable, maxlen=None): + self._source = iter(iterable) + if maxlen is None: + self._cache = [] + else: + self._cache = deque([], maxlen) + self._index = None + + def __iter__(self): + return self + + def __next__(self): + if self._index is not None: + try: + item = self._cache[self._index] + except IndexError: + self._index = None + else: + self._index += 1 + return item + + item = next(self._source) + self._cache.append(item) + return item + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + try: + peeked = next(self) + except StopIteration: + if default is _marker: + raise + return default + if self._index is None: + self._index = len(self._cache) + self._index -= 1 + return peeked + + def elements(self): + return SequenceView(self._cache) + + def seek(self, index): + self._index = index + remainder = index - len(self._cache) + if remainder > 0: + consume(self, remainder) + + def relative_seek(self, count): + index = len(self._cache) + self.seek(max(index + count, 0)) + + +class run_length: + """ + :func:`run_length.encode` compresses an iterable with run-length encoding. + It yields groups of repeated items with the count of how many times they + were repeated: + + >>> uncompressed = 'abbcccdddd' + >>> list(run_length.encode(uncompressed)) + [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + + :func:`run_length.decode` decompresses an iterable that was previously + compressed with run-length encoding. It yields the items of the + decompressed iterable: + + >>> compressed = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> list(run_length.decode(compressed)) + ['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'] + + """ + + @staticmethod + def encode(iterable): + return ((k, ilen(g)) for k, g in groupby(iterable)) + + @staticmethod + def decode(iterable): + return chain.from_iterable(repeat(k, n) for k, n in iterable) + + +def exactly_n(iterable, n, predicate=bool): + """Return ``True`` if exactly ``n`` items in the iterable are ``True`` + according to the *predicate* function. + + >>> exactly_n([True, True, False], 2) + True + >>> exactly_n([True, True, False], 1) + False + >>> exactly_n([0, 1, 2, 3, 4, 5], 3, lambda x: x < 3) + True + + The iterable will be advanced until ``n + 1`` truthy items are encountered, + so avoid calling it on infinite iterables. + + """ + return len(take(n + 1, filter(predicate, iterable))) == n + + +def circular_shifts(iterable): + """Return a list of circular shifts of *iterable*. + + >>> circular_shifts(range(4)) + [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)] + """ + lst = list(iterable) + return take(len(lst), windowed(cycle(lst), len(lst))) + + +def make_decorator(wrapping_func, result_index=0): + """Return a decorator version of *wrapping_func*, which is a function that + modifies an iterable. *result_index* is the position in that function's + signature where the iterable goes. + + This lets you use itertools on the "production end," i.e. at function + definition. This can augment what the function returns without changing the + function's code. + + For example, to produce a decorator version of :func:`chunked`: + + >>> from more_itertools import chunked + >>> chunker = make_decorator(chunked, result_index=0) + >>> @chunker(3) + ... def iter_range(n): + ... return iter(range(n)) + ... + >>> list(iter_range(9)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + + To only allow truthy items to be returned: + + >>> truth_serum = make_decorator(filter, result_index=1) + >>> @truth_serum(bool) + ... def boolean_test(): + ... return [0, 1, '', ' ', False, True] + ... + >>> list(boolean_test()) + [1, ' ', True] + + The :func:`peekable` and :func:`seekable` wrappers make for practical + decorators: + + >>> from more_itertools import peekable + >>> peekable_function = make_decorator(peekable) + >>> @peekable_function() + ... def str_range(*args): + ... return (str(x) for x in range(*args)) + ... + >>> it = str_range(1, 20, 2) + >>> next(it), next(it), next(it) + ('1', '3', '5') + >>> it.peek() + '7' + >>> next(it) + '7' + + """ + + # See https://sites.google.com/site/bbayles/index/decorator_factory for + # notes on how this works. + def decorator(*wrapping_args, **wrapping_kwargs): + def outer_wrapper(f): + def inner_wrapper(*args, **kwargs): + result = f(*args, **kwargs) + wrapping_args_ = list(wrapping_args) + wrapping_args_.insert(result_index, result) + return wrapping_func(*wrapping_args_, **wrapping_kwargs) + + return inner_wrapper + + return outer_wrapper + + return decorator + + +def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None): + """Return a dictionary that maps the items in *iterable* to categories + defined by *keyfunc*, transforms them with *valuefunc*, and + then summarizes them by category with *reducefunc*. + + *valuefunc* defaults to the identity function if it is unspecified. + If *reducefunc* is unspecified, no summarization takes place: + + >>> keyfunc = lambda x: x.upper() + >>> result = map_reduce('abbccc', keyfunc) + >>> sorted(result.items()) + [('A', ['a']), ('B', ['b', 'b']), ('C', ['c', 'c', 'c'])] + + Specifying *valuefunc* transforms the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> result = map_reduce('abbccc', keyfunc, valuefunc) + >>> sorted(result.items()) + [('A', [1]), ('B', [1, 1]), ('C', [1, 1, 1])] + + Specifying *reducefunc* summarizes the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> reducefunc = sum + >>> result = map_reduce('abbccc', keyfunc, valuefunc, reducefunc) + >>> sorted(result.items()) + [('A', 1), ('B', 2), ('C', 3)] + + You may want to filter the input iterable before applying the map/reduce + procedure: + + >>> all_items = range(30) + >>> items = [x for x in all_items if 10 <= x <= 20] # Filter + >>> keyfunc = lambda x: x % 2 # Evens map to 0; odds to 1 + >>> categories = map_reduce(items, keyfunc=keyfunc) + >>> sorted(categories.items()) + [(0, [10, 12, 14, 16, 18, 20]), (1, [11, 13, 15, 17, 19])] + >>> summaries = map_reduce(items, keyfunc=keyfunc, reducefunc=sum) + >>> sorted(summaries.items()) + [(0, 90), (1, 75)] + + Note that all items in the iterable are gathered into a list before the + summarization step, which may require significant storage. + + The returned object is a :obj:`collections.defaultdict` with the + ``default_factory`` set to ``None``, such that it behaves like a normal + dictionary. + + """ + valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc + + ret = defaultdict(list) + for item in iterable: + key = keyfunc(item) + value = valuefunc(item) + ret[key].append(value) + + if reducefunc is not None: + for key, value_list in ret.items(): + ret[key] = reducefunc(value_list) + + ret.default_factory = None + return ret + + +def rlocate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``, starting from the right and moving left. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4 + [4, 2, 1] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item: + + >>> iterable = iter('abcb') + >>> pred = lambda x: x == 'b' + >>> list(rlocate(iterable, pred)) + [3, 1] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(rlocate(iterable, pred=pred, window_size=3)) + [9, 5, 1] + + Beware, this function won't return anything for infinite iterables. + If *iterable* is reversible, ``rlocate`` will reverse it and search from + the right. Otherwise, it will search from the left and return the results + in reverse order. + + See :func:`locate` to for other example applications. + + """ + if window_size is None: + try: + len_iter = len(iterable) + return (len_iter - i - 1 for i in locate(reversed(iterable), pred)) + except TypeError: + pass + + return reversed(list(locate(iterable, pred, window_size))) + + +def replace(iterable, pred, substitutes, count=None, window_size=1): + """Yield the items from *iterable*, replacing the items for which *pred* + returns ``True`` with the items from the iterable *substitutes*. + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1] + >>> pred = lambda x: x == 0 + >>> substitutes = (2, 3) + >>> list(replace(iterable, pred, substitutes)) + [1, 1, 2, 3, 1, 1, 2, 3, 1, 1] + + If *count* is given, the number of replacements will be limited: + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1, 0] + >>> pred = lambda x: x == 0 + >>> substitutes = [None] + >>> list(replace(iterable, pred, substitutes, count=2)) + [1, 1, None, 1, 1, None, 1, 1, 0] + + Use *window_size* to control the number of items passed as arguments to + *pred*. This allows for locating and replacing subsequences. + + >>> iterable = [0, 1, 2, 5, 0, 1, 2, 5] + >>> window_size = 3 + >>> pred = lambda *args: args == (0, 1, 2) # 3 items passed to pred + >>> substitutes = [3, 4] # Splice in these items + >>> list(replace(iterable, pred, substitutes, window_size=window_size)) + [3, 4, 5, 3, 4, 5] + + """ + if window_size < 1: + raise ValueError('window_size must be at least 1') + + # Save the substitutes iterable, since it's used more than once + substitutes = tuple(substitutes) + + # Add padding such that the number of windows matches the length of the + # iterable + it = chain(iterable, [_marker] * (window_size - 1)) + windows = windowed(it, window_size) + + n = 0 + for w in windows: + # If the current window matches our predicate (and we haven't hit + # our maximum number of replacements), splice in the substitutes + # and then consume the following windows that overlap with this one. + # For example, if the iterable is (0, 1, 2, 3, 4...) + # and the window size is 2, we have (0, 1), (1, 2), (2, 3)... + # If the predicate matches on (0, 1), we need to zap (0, 1) and (1, 2) + if pred(*w): + if (count is None) or (n < count): + n += 1 + yield from substitutes + consume(windows, window_size - 1) + continue + + # If there was no match (or we've reached the replacement limit), + # yield the first item from the window. + if w and (w[0] is not _marker): + yield w[0] + + +def partitions(iterable): + """Yield all possible order-preserving partitions of *iterable*. + + >>> iterable = 'abc' + >>> for part in partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['a', 'b', 'c'] + + This is unrelated to :func:`partition`. + + """ + sequence = list(iterable) + n = len(sequence) + for i in powerset(range(1, n)): + yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))] + + +def set_partitions(iterable, k=None): + """ + Yield the set partitions of *iterable* into *k* parts. Set partitions are + not order-preserving. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable, 2): + ... print([''.join(p) for p in part]) + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + + + If *k* is not given, every set partition is generated. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + ['a', 'b', 'c'] + + """ + L = list(iterable) + n = len(L) + if k is not None: + if k < 1: + raise ValueError( + "Can't partition in a negative or zero number of groups" + ) + elif k > n: + return + + def set_partitions_helper(L, k): + n = len(L) + if k == 1: + yield [L] + elif n == k: + yield [[s] for s in L] + else: + e, *M = L + for p in set_partitions_helper(M, k - 1): + yield [[e], *p] + for p in set_partitions_helper(M, k): + for i in range(len(p)): + yield p[:i] + [[e] + p[i]] + p[i + 1 :] + + if k is None: + for k in range(1, n + 1): + yield from set_partitions_helper(L, k) + else: + yield from set_partitions_helper(L, k) + + +class time_limited: + """ + Yield items from *iterable* until *limit_seconds* have passed. + If the time limit expires before all items have been yielded, the + ``timed_out`` parameter will be set to ``True``. + + >>> from time import sleep + >>> def generator(): + ... yield 1 + ... yield 2 + ... sleep(0.2) + ... yield 3 + >>> iterable = time_limited(0.1, generator()) + >>> list(iterable) + [1, 2] + >>> iterable.timed_out + True + + Note that the time is checked before each item is yielded, and iteration + stops if the time elapsed is greater than *limit_seconds*. If your time + limit is 1 second, but it takes 2 seconds to generate the first item from + the iterable, the function will run for 2 seconds and not yield anything. + As a special case, when *limit_seconds* is zero, the iterator never + returns anything. + + """ + + def __init__(self, limit_seconds, iterable): + if limit_seconds < 0: + raise ValueError('limit_seconds must be positive') + self.limit_seconds = limit_seconds + self._iterable = iter(iterable) + self._start_time = monotonic() + self.timed_out = False + + def __iter__(self): + return self + + def __next__(self): + if self.limit_seconds == 0: + self.timed_out = True + raise StopIteration + item = next(self._iterable) + if monotonic() - self._start_time > self.limit_seconds: + self.timed_out = True + raise StopIteration + + return item + + +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +class _IChunk: + def __init__(self, iterable, n): + self._it = islice(iterable, n) + self._cache = deque() + + def fill_cache(self): + self._cache.extend(self._it) + + def __iter__(self): + return self + + def __next__(self): + try: + return next(self._it) + except StopIteration: + if self._cache: + return self._cache.popleft() + else: + raise + + +def ichunked(iterable, n): + """Break *iterable* into sub-iterables with *n* elements each. + :func:`ichunked` is like :func:`chunked`, but it yields iterables + instead of lists. + + If the sub-iterables are read in order, the elements of *iterable* + won't be stored in memory. + If they are read out of order, :func:`itertools.tee` is used to cache + elements as necessary. + + >>> from itertools import count + >>> all_chunks = ichunked(count(), 4) + >>> c_1, c_2, c_3 = next(all_chunks), next(all_chunks), next(all_chunks) + >>> list(c_2) # c_1's elements have been cached; c_3's haven't been + [4, 5, 6, 7] + >>> list(c_1) + [0, 1, 2, 3] + >>> list(c_3) + [8, 9, 10, 11] + + """ + source = peekable(iter(iterable)) + ichunk_marker = object() + while True: + # Check to see whether we're at the end of the source iterable + item = source.peek(ichunk_marker) + if item is ichunk_marker: + return + + chunk = _IChunk(source, n) + yield chunk + + # Advance the source iterable and fill previous chunk's cache + chunk.fill_cache() + + +def iequals(*iterables): + """Return ``True`` if all given *iterables* are equal to each other, + which means that they contain the same elements in the same order. + + The function is useful for comparing iterables of different data types + or iterables that do not support equality checks. + + >>> iequals("abc", ['a', 'b', 'c'], ('a', 'b', 'c'), iter("abc")) + True + + >>> iequals("abc", "acb") + False + + Not to be confused with :func:`all_equal`, which checks whether all + elements of iterable are equal to each other. + + """ + return all(map(all_equal, zip_longest(*iterables, fillvalue=object()))) + + +def distinct_combinations(iterable, r): + """Yield the distinct combinations of *r* items taken from *iterable*. + + >>> list(distinct_combinations([0, 0, 1], 2)) + [(0, 0), (0, 1)] + + Equivalent to ``set(combinations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + """ + if r < 0: + raise ValueError('r must be non-negative') + elif r == 0: + yield () + return + pool = tuple(iterable) + generators = [unique_everseen(enumerate(pool), key=itemgetter(1))] + current_combo = [None] * r + level = 0 + while generators: + try: + cur_idx, p = next(generators[-1]) + except StopIteration: + generators.pop() + level -= 1 + continue + current_combo[level] = p + if level + 1 == r: + yield tuple(current_combo) + else: + generators.append( + unique_everseen( + enumerate(pool[cur_idx + 1 :], cur_idx + 1), + key=itemgetter(1), + ) + ) + level += 1 + + +def filter_except(validator, iterable, *exceptions): + """Yield the items from *iterable* for which the *validator* function does + not raise one of the specified *exceptions*. + + *validator* is called for each item in *iterable*. + It should be a function that accepts one argument and raises an exception + if that item is not valid. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(filter_except(int, iterable, ValueError, TypeError)) + ['1', '2', '4'] + + If an exception other than one given by *exceptions* is raised by + *validator*, it is raised like normal. + """ + for item in iterable: + try: + validator(item) + except exceptions: + pass + else: + yield item + + +def map_except(function, iterable, *exceptions): + """Transform each item from *iterable* with *function* and yield the + result, unless *function* raises one of the specified *exceptions*. + + *function* is called to transform each item in *iterable*. + It should accept one argument. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(map_except(int, iterable, ValueError, TypeError)) + [1, 2, 4] + + If an exception other than one given by *exceptions* is raised by + *function*, it is raised like normal. + """ + for item in iterable: + try: + yield function(item) + except exceptions: + pass + + +def map_if(iterable, pred, func, func_else=lambda x: x): + """Evaluate each item from *iterable* using *pred*. If the result is + equivalent to ``True``, transform the item with *func* and yield it. + Otherwise, transform the item with *func_else* and yield it. + + *pred*, *func*, and *func_else* should each be functions that accept + one argument. By default, *func_else* is the identity function. + + >>> from math import sqrt + >>> iterable = list(range(-5, 5)) + >>> iterable + [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] + >>> list(map_if(iterable, lambda x: x > 3, lambda x: 'toobig')) + [-5, -4, -3, -2, -1, 0, 1, 2, 3, 'toobig'] + >>> list(map_if(iterable, lambda x: x >= 0, + ... lambda x: f'{sqrt(x):.2f}', lambda x: None)) + [None, None, None, None, None, '0.00', '1.00', '1.41', '1.73', '2.00'] + """ + for item in iterable: + yield func(item) if pred(item) else func_else(item) + + +def _sample_unweighted(iterable, k): + # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li: + # "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))". + + # Fill up the reservoir (collection of samples) with the first `k` samples + reservoir = take(k, iterable) + + # Generate random number that's the largest in a sample of k U(0,1) numbers + # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic + W = exp(log(random()) / k) + + # The number of elements to skip before changing the reservoir is a random + # number with a geometric distribution. Sample it using random() and logs. + next_index = k + floor(log(random()) / log(1 - W)) + + for index, element in enumerate(iterable, k): + if index == next_index: + reservoir[randrange(k)] = element + # The new W is the largest in a sample of k U(0, `old_W`) numbers + W *= exp(log(random()) / k) + next_index += floor(log(random()) / log(1 - W)) + 1 + + return reservoir + + +def _sample_weighted(iterable, k, weights): + # Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. : + # "Weighted random sampling with a reservoir". + + # Log-transform for numerical stability for weights that are small/large + weight_keys = (log(random()) / weight for weight in weights) + + # Fill up the reservoir (collection of samples) with the first `k` + # weight-keys and elements, then heapify the list. + reservoir = take(k, zip(weight_keys, iterable)) + heapify(reservoir) + + # The number of jumps before changing the reservoir is a random variable + # with an exponential distribution. Sample it using random() and logs. + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + + for weight, element in zip(weights, iterable): + if weight >= weights_to_skip: + # The notation here is consistent with the paper, but we store + # the weight-keys in log-space for better numerical stability. + smallest_weight_key, _ = reservoir[0] + t_w = exp(weight * smallest_weight_key) + r_2 = uniform(t_w, 1) # generate U(t_w, 1) + weight_key = log(r_2) / weight + heapreplace(reservoir, (weight_key, element)) + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + else: + weights_to_skip -= weight + + # Equivalent to [element for weight_key, element in sorted(reservoir)] + return [heappop(reservoir)[1] for _ in range(k)] + + +def sample(iterable, k, weights=None): + """Return a *k*-length list of elements chosen (without replacement) + from the *iterable*. Like :func:`random.sample`, but works on iterables + of unknown length. + + >>> iterable = range(100) + >>> sample(iterable, 5) # doctest: +SKIP + [81, 60, 96, 16, 4] + + An iterable with *weights* may also be given: + + >>> iterable = range(100) + >>> weights = (i * i + 1 for i in range(100)) + >>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP + [79, 67, 74, 66, 78] + + The algorithm can also be used to generate weighted random permutations. + The relative weight of each item determines the probability that it + appears late in the permutation. + + >>> data = "abcdefgh" + >>> weights = range(1, len(data) + 1) + >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP + ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f'] + """ + if k == 0: + return [] + + iterable = iter(iterable) + if weights is None: + return _sample_unweighted(iterable, k) + else: + weights = iter(weights) + return _sample_weighted(iterable, k, weights) + + +def is_sorted(iterable, key=None, reverse=False, strict=False): + """Returns ``True`` if the items of iterable are in sorted order, and + ``False`` otherwise. *key* and *reverse* have the same meaning that they do + in the built-in :func:`sorted` function. + + >>> is_sorted(['1', '2', '3', '4', '5'], key=int) + True + >>> is_sorted([5, 4, 3, 1, 2], reverse=True) + False + + If *strict*, tests for strict sorting, that is, returns ``False`` if equal + elements are found: + + >>> is_sorted([1, 2, 2]) + True + >>> is_sorted([1, 2, 2], strict=True) + False + + The function returns ``False`` after encountering the first out-of-order + item. If there are no out-of-order items, the iterable is exhausted. + """ + + compare = (le if reverse else ge) if strict else (lt if reverse else gt) + it = iterable if key is None else map(key, iterable) + return not any(starmap(compare, pairwise(it))) + + +class AbortThread(BaseException): + pass + + +class callback_iter: + """Convert a function that uses callbacks to an iterator. + + Let *func* be a function that takes a `callback` keyword argument. + For example: + + >>> def func(callback=None): + ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]: + ... if callback: + ... callback(i, c) + ... return 4 + + + Use ``with callback_iter(func)`` to get an iterator over the parameters + that are delivered to the callback. + + >>> with callback_iter(func) as it: + ... for args, kwargs in it: + ... print(args) + (1, 'a') + (2, 'b') + (3, 'c') + + The function will be called in a background thread. The ``done`` property + indicates whether it has completed execution. + + >>> it.done + True + + If it completes successfully, its return value will be available + in the ``result`` property. + + >>> it.result + 4 + + Notes: + + * If the function uses some keyword argument besides ``callback``, supply + *callback_kwd*. + * If it finished executing, but raised an exception, accessing the + ``result`` property will raise the same exception. + * If it hasn't finished executing, accessing the ``result`` + property from within the ``with`` block will raise ``RuntimeError``. + * If it hasn't finished executing, accessing the ``result`` property from + outside the ``with`` block will raise a + ``more_itertools.AbortThread`` exception. + * Provide *wait_seconds* to adjust how frequently the it is polled for + output. + + """ + + def __init__(self, func, callback_kwd='callback', wait_seconds=0.1): + self._func = func + self._callback_kwd = callback_kwd + self._aborted = False + self._future = None + self._wait_seconds = wait_seconds + # Lazily import concurrent.future + self._executor = __import__( + ).futures.__import__("concurrent.futures").futures.ThreadPoolExecutor(max_workers=1) + self._iterator = self._reader() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._aborted = True + self._executor.shutdown() + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterator) + + @property + def done(self): + if self._future is None: + return False + return self._future.done() + + @property + def result(self): + if not self.done: + raise RuntimeError('Function has not yet completed') + + return self._future.result() + + def _reader(self): + q = Queue() + + def callback(*args, **kwargs): + if self._aborted: + raise AbortThread('canceled by user') + + q.put((args, kwargs)) + + self._future = self._executor.submit( + self._func, **{self._callback_kwd: callback} + ) + + while True: + try: + item = q.get(timeout=self._wait_seconds) + except Empty: + pass + else: + q.task_done() + yield item + + if self._future.done(): + break + + remaining = [] + while True: + try: + item = q.get_nowait() + except Empty: + break + else: + q.task_done() + remaining.append(item) + q.join() + yield from remaining + + +def windowed_complete(iterable, n): + """ + Yield ``(beginning, middle, end)`` tuples, where: + + * Each ``middle`` has *n* items from *iterable* + * Each ``beginning`` has the items before the ones in ``middle`` + * Each ``end`` has the items after the ones in ``middle`` + + >>> iterable = range(7) + >>> n = 3 + >>> for beginning, middle, end in windowed_complete(iterable, n): + ... print(beginning, middle, end) + () (0, 1, 2) (3, 4, 5, 6) + (0,) (1, 2, 3) (4, 5, 6) + (0, 1) (2, 3, 4) (5, 6) + (0, 1, 2) (3, 4, 5) (6,) + (0, 1, 2, 3) (4, 5, 6) () + + Note that *n* must be at least 0 and most equal to the length of + *iterable*. + + This function will exhaust the iterable and may require significant + storage. + """ + if n < 0: + raise ValueError('n must be >= 0') + + seq = tuple(iterable) + size = len(seq) + + if n > size: + raise ValueError('n must be <= len(seq)') + + for i in range(size - n + 1): + beginning = seq[:i] + middle = seq[i : i + n] + end = seq[i + n :] + yield beginning, middle, end + + +def all_unique(iterable, key=None): + """ + Returns ``True`` if all the elements of *iterable* are unique (no two + elements are equal). + + >>> all_unique('ABCB') + False + + If a *key* function is specified, it will be used to make comparisons. + + >>> all_unique('ABCb') + True + >>> all_unique('ABCb', str.lower) + False + + The function returns as soon as the first non-unique element is + encountered. Iterables with a mix of hashable and unhashable items can + be used, but the function will be slower for unhashable items. + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + for element in map(key, iterable) if key else iterable: + try: + if element in seenset: + return False + seenset_add(element) + except TypeError: + if element in seenlist: + return False + seenlist_add(element) + return True + + +def nth_product(index, *args): + """Equivalent to ``list(product(*args))[index]``. + + The products of *args* can be ordered lexicographically. + :func:`nth_product` computes the product at sort position *index* without + computing the previous products. + + >>> nth_product(8, range(2), range(2), range(2), range(2)) + (1, 0, 0, 0) + + ``IndexError`` will be raised if the given *index* is invalid. + """ + pools = list(map(tuple, reversed(args))) + ns = list(map(len, pools)) + + c = reduce(mul, ns) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + result = [] + for pool, n in zip(pools, ns): + result.append(pool[index % n]) + index //= n + + return tuple(reversed(result)) + + +def nth_permutation(iterable, r, index): + """Equivalent to ``list(permutations(iterable, r))[index]``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`nth_permutation` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences. + + >>> nth_permutation('ghijk', 2, 5) + ('h', 'i') + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = list(iterable) + n = len(pool) + + if r is None or r == n: + r, c = n, factorial(n) + elif not 0 <= r < n: + raise ValueError + else: + c = perm(n, r) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + if c == 0: + return tuple() + + result = [0] * r + q = index * factorial(n) // c if r < n else index + for d in range(1, n + 1): + q, i = divmod(q, d) + if 0 <= n - d < r: + result[n - d] = i + if q == 0: + break + + return tuple(map(pool.pop, result)) + + +def nth_combination_with_replacement(iterable, r, index): + """Equivalent to + ``list(combinations_with_replacement(iterable, r))[index]``. + + + The subsequences with repetition of *iterable* that are of length *r* can + be ordered lexicographically. :func:`nth_combination_with_replacement` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences with replacement. + + >>> nth_combination_with_replacement(range(5), 3, 5) + (0, 1, 1) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = comb(n + r - 1, r) + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + i = 0 + while r: + r -= 1 + while n >= 0: + num_combs = comb(n + r - 1, r) + if index < num_combs: + break + n -= 1 + i += 1 + index -= num_combs + result.append(pool[i]) + + return tuple(result) + + +def value_chain(*args): + """Yield all arguments passed to the function in the same order in which + they were passed. If an argument itself is iterable then iterate over its + values. + + >>> list(value_chain(1, 2, 3, [4, 5, 6])) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and are emitted + as-is: + + >>> list(value_chain('12', '34', ['56', '78'])) + ['12', '34', '56', '78'] + + + Multiple levels of nesting are not flattened. + + """ + for value in args: + if isinstance(value, (str, bytes)): + yield value + continue + try: + yield from value + except TypeError: + yield value + + +def product_index(element, *args): + """Equivalent to ``list(product(*args)).index(element)`` + + The products of *args* can be ordered lexicographically. + :func:`product_index` computes the first index of *element* without + computing the previous products. + + >>> product_index([8, 2], range(10), range(5)) + 42 + + ``ValueError`` will be raised if the given *element* isn't in the product + of *args*. + """ + index = 0 + + for x, pool in zip_longest(element, args, fillvalue=_marker): + if x is _marker or pool is _marker: + raise ValueError('element is not a product of args') + + pool = tuple(pool) + index = index * len(pool) + pool.index(x) + + return index + + +def combination_index(element, iterable): + """Equivalent to ``list(combinations(iterable, r)).index(element)`` + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`combination_index` computes the index of the + first *element*, without computing the previous combinations. + + >>> combination_index('adf', 'abcdefg') + 10 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations of *iterable*. + """ + element = enumerate(element) + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = enumerate(iterable) + for n, x in pool: + if x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + else: + raise ValueError('element is not a combination of iterable') + + n, _ = last(pool, default=(n, None)) + + # Python versions below 3.8 don't have math.comb + index = 1 + for i, j in enumerate(reversed(indexes), start=1): + j = n - j + if i <= j: + index += comb(j, i) + + return comb(n + 1, k + 1) - index + + +def combination_with_replacement_index(element, iterable): + """Equivalent to + ``list(combinations_with_replacement(iterable, r)).index(element)`` + + The subsequences with repetition of *iterable* that are of length *r* can + be ordered lexicographically. :func:`combination_with_replacement_index` + computes the index of the first *element*, without computing the previous + combinations with replacement. + + >>> combination_with_replacement_index('adf', 'abcdefg') + 20 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations with replacement of *iterable*. + """ + element = tuple(element) + l = len(element) + element = enumerate(element) + + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = tuple(iterable) + for n, x in enumerate(pool): + while x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + if y is None: + break + else: + raise ValueError( + 'element is not a combination with replacement of iterable' + ) + + n = len(pool) + occupations = [0] * n + for p in indexes: + occupations[p] += 1 + + index = 0 + cumulative_sum = 0 + for k in range(1, n): + cumulative_sum += occupations[k - 1] + j = l + n - 1 - k - cumulative_sum + i = n - k + if i <= j: + index += comb(j, i) + + return index + + +def permutation_index(element, iterable): + """Equivalent to ``list(permutations(iterable, r)).index(element)``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`permutation_index` + computes the index of the first *element* directly, without computing + the previous permutations. + + >>> permutation_index([1, 3, 2], range(5)) + 19 + + ``ValueError`` will be raised if the given *element* isn't one of the + permutations of *iterable*. + """ + index = 0 + pool = list(iterable) + for i, x in zip(range(len(pool), -1, -1), element): + r = pool.index(x) + index = index * i + r + del pool[r] + + return index + + +class countable: + """Wrap *iterable* and keep a count of how many items have been consumed. + + The ``items_seen`` attribute starts at ``0`` and increments as the iterable + is consumed: + + >>> iterable = map(str, range(10)) + >>> it = countable(iterable) + >>> it.items_seen + 0 + >>> next(it), next(it) + ('0', '1') + >>> list(it) + ['2', '3', '4', '5', '6', '7', '8', '9'] + >>> it.items_seen + 10 + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self.items_seen = 0 + + def __iter__(self): + return self + + def __next__(self): + item = next(self._it) + self.items_seen += 1 + + return item + + +def chunked_even(iterable, n): + """Break *iterable* into lists of approximately length *n*. + Items are distributed such the lengths of the lists differ by at most + 1 item. + + >>> iterable = [1, 2, 3, 4, 5, 6, 7] + >>> n = 3 + >>> list(chunked_even(iterable, n)) # List lengths: 3, 2, 2 + [[1, 2, 3], [4, 5], [6, 7]] + >>> list(chunked(iterable, n)) # List lengths: 3, 3, 1 + [[1, 2, 3], [4, 5, 6], [7]] + + """ + + len_method = getattr(iterable, '__len__', None) + + if len_method is None: + return _chunked_even_online(iterable, n) + else: + return _chunked_even_finite(iterable, len_method(), n) + + +def _chunked_even_online(iterable, n): + buffer = [] + maxbuf = n + (n - 2) * (n - 1) + for x in iterable: + buffer.append(x) + if len(buffer) == maxbuf: + yield buffer[:n] + buffer = buffer[n:] + yield from _chunked_even_finite(buffer, len(buffer), n) + + +def _chunked_even_finite(iterable, N, n): + if N < 1: + return + + # Lists are either size `full_size <= n` or `partial_size = full_size - 1` + q, r = divmod(N, n) + num_lists = q + (1 if r > 0 else 0) + q, r = divmod(N, num_lists) + full_size = q + (1 if r > 0 else 0) + partial_size = full_size - 1 + num_full = N - partial_size * num_lists + num_partial = num_lists - num_full + + # Yield num_full lists of full_size + partial_start_idx = num_full * full_size + if full_size > 0: + for i in range(0, partial_start_idx, full_size): + yield list(islice(iterable, i, i + full_size)) + + # Yield num_partial lists of partial_size + if partial_size > 0: + for i in range( + partial_start_idx, + partial_start_idx + (num_partial * partial_size), + partial_size, + ): + yield list(islice(iterable, i, i + partial_size)) + + +def zip_broadcast(*objects, scalar_types=(str, bytes), strict=False): + """A version of :func:`zip` that "broadcasts" any scalar + (i.e., non-iterable) items into output tuples. + + >>> iterable_1 = [1, 2, 3] + >>> iterable_2 = ['a', 'b', 'c'] + >>> scalar = '_' + >>> list(zip_broadcast(iterable_1, iterable_2, scalar)) + [(1, 'a', '_'), (2, 'b', '_'), (3, 'c', '_')] + + The *scalar_types* keyword argument determines what types are considered + scalar. It is set to ``(str, bytes)`` by default. Set it to ``None`` to + treat strings and byte strings as iterable: + + >>> list(zip_broadcast('abc', 0, 'xyz', scalar_types=None)) + [('a', 0, 'x'), ('b', 0, 'y'), ('c', 0, 'z')] + + If the *strict* keyword argument is ``True``, then + ``UnequalIterablesError`` will be raised if any of the iterables have + different lengths. + """ + + def is_scalar(obj): + if scalar_types and isinstance(obj, scalar_types): + return True + try: + iter(obj) + except TypeError: + return True + else: + return False + + size = len(objects) + if not size: + return + + new_item = [None] * size + iterables, iterable_positions = [], [] + for i, obj in enumerate(objects): + if is_scalar(obj): + new_item[i] = obj + else: + iterables.append(iter(obj)) + iterable_positions.append(i) + + if not iterables: + yield tuple(objects) + return + + zipper = _zip_equal if strict else zip + for item in zipper(*iterables): + for i, new_item[i] in zip(iterable_positions, item): + pass + yield tuple(new_item) + + +def unique_in_window(iterable, n, key=None): + """Yield the items from *iterable* that haven't been seen recently. + *n* is the size of the lookback window. + + >>> iterable = [0, 1, 0, 2, 3, 0] + >>> n = 3 + >>> list(unique_in_window(iterable, n)) + [0, 1, 2, 3, 0] + + The *key* function, if provided, will be used to determine uniqueness: + + >>> list(unique_in_window('abAcda', 3, key=lambda x: x.lower())) + ['a', 'b', 'c', 'd', 'a'] + + The items in *iterable* must be hashable. + + """ + if n <= 0: + raise ValueError('n must be greater than 0') + + window = deque(maxlen=n) + counts = defaultdict(int) + use_key = key is not None + + for item in iterable: + if len(window) == n: + to_discard = window[0] + if counts[to_discard] == 1: + del counts[to_discard] + else: + counts[to_discard] -= 1 + + k = key(item) if use_key else item + if k not in counts: + yield item + counts[k] += 1 + window.append(k) + + +def duplicates_everseen(iterable, key=None): + """Yield duplicate elements after their first appearance. + + >>> list(duplicates_everseen('mississippi')) + ['s', 'i', 's', 's', 'i', 'p', 'i'] + >>> list(duplicates_everseen('AaaBbbCccAaa', str.lower)) + ['a', 'a', 'b', 'b', 'c', 'c', 'A', 'a', 'a'] + + This function is analogous to :func:`unique_everseen` and is subject to + the same performance considerations. + + """ + seen_set = set() + seen_list = [] + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seen_set: + seen_set.add(k) + else: + yield element + except TypeError: + if k not in seen_list: + seen_list.append(k) + else: + yield element + + +def duplicates_justseen(iterable, key=None): + """Yields serially-duplicate elements after their first appearance. + + >>> list(duplicates_justseen('mississippi')) + ['s', 's', 'p'] + >>> list(duplicates_justseen('AaaBbbCccAaa', str.lower)) + ['a', 'a', 'b', 'b', 'c', 'c', 'a', 'a'] + + This function is analogous to :func:`unique_justseen`. + + """ + return flatten(g for _, g in groupby(iterable, key) for _ in g) + + +def classify_unique(iterable, key=None): + """Classify each element in terms of its uniqueness. + + For each element in the input iterable, return a 3-tuple consisting of: + + 1. The element itself + 2. ``False`` if the element is equal to the one preceding it in the input, + ``True`` otherwise (i.e. the equivalent of :func:`unique_justseen`) + 3. ``False`` if this element has been seen anywhere in the input before, + ``True`` otherwise (i.e. the equivalent of :func:`unique_everseen`) + + >>> list(classify_unique('otto')) # doctest: +NORMALIZE_WHITESPACE + [('o', True, True), + ('t', True, True), + ('t', False, False), + ('o', True, False)] + + This function is analogous to :func:`unique_everseen` and is subject to + the same performance considerations. + + """ + seen_set = set() + seen_list = [] + use_key = key is not None + previous = None + + for i, element in enumerate(iterable): + k = key(element) if use_key else element + is_unique_justseen = not i or previous != k + previous = k + is_unique_everseen = False + try: + if k not in seen_set: + seen_set.add(k) + is_unique_everseen = True + except TypeError: + if k not in seen_list: + seen_list.append(k) + is_unique_everseen = True + yield element, is_unique_justseen, is_unique_everseen + + +def minmax(iterable_or_value, *others, key=None, default=_marker): + """Returns both the smallest and largest items in an iterable + or the largest of two or more arguments. + + >>> minmax([3, 1, 5]) + (1, 5) + + >>> minmax(4, 2, 6) + (2, 6) + + If a *key* function is provided, it will be used to transform the input + items for comparison. + + >>> minmax([5, 30], key=str) # '30' sorts before '5' + (30, 5) + + If a *default* value is provided, it will be returned if there are no + input items. + + >>> minmax([], default=(0, 0)) + (0, 0) + + Otherwise ``ValueError`` is raised. + + This function is based on the + `recipe `__ by + Raymond Hettinger and takes care to minimize the number of comparisons + performed. + """ + iterable = (iterable_or_value, *others) if others else iterable_or_value + + it = iter(iterable) + + try: + lo = hi = next(it) + except StopIteration as e: + if default is _marker: + raise ValueError( + '`minmax()` argument is an empty iterable. ' + 'Provide a `default` value to suppress this error.' + ) from e + return default + + # Different branches depending on the presence of key. This saves a lot + # of unimportant copies which would slow the "key=None" branch + # significantly down. + if key is None: + for x, y in zip_longest(it, it, fillvalue=lo): + if y < x: + x, y = y, x + if x < lo: + lo = x + if hi < y: + hi = y + + else: + lo_key = hi_key = key(lo) + + for x, y in zip_longest(it, it, fillvalue=lo): + x_key, y_key = key(x), key(y) + + if y_key < x_key: + x, y, x_key, y_key = y, x, y_key, x_key + if x_key < lo_key: + lo, lo_key = x, x_key + if hi_key < y_key: + hi, hi_key = y, y_key + + return lo, hi + + +def constrained_batches( + iterable, max_size, max_count=None, get_len=len, strict=True +): + """Yield batches of items from *iterable* with a combined size limited by + *max_size*. + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10)) + [(b'12345', b'123'), (b'12345678', b'1', b'1'), (b'12', b'1')] + + If a *max_count* is supplied, the number of items per batch is also + limited: + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10, max_count = 2)) + [(b'12345', b'123'), (b'12345678', b'1'), (b'1', b'12'), (b'1',)] + + If a *get_len* function is supplied, use that instead of :func:`len` to + determine item size. + + If *strict* is ``True``, raise ``ValueError`` if any single item is bigger + than *max_size*. Otherwise, allow single items to exceed *max_size*. + """ + if max_size <= 0: + raise ValueError('maximum size must be greater than zero') + + batch = [] + batch_size = 0 + batch_count = 0 + for item in iterable: + item_len = get_len(item) + if strict and item_len > max_size: + raise ValueError('item size exceeds maximum size') + + reached_count = batch_count == max_count + reached_size = item_len + batch_size > max_size + if batch_count and (reached_size or reached_count): + yield tuple(batch) + batch.clear() + batch_size = 0 + batch_count = 0 + + batch.append(item) + batch_size += item_len + batch_count += 1 + + if batch: + yield tuple(batch) + + +def gray_product(*iterables): + """Like :func:`itertools.product`, but return tuples in an order such + that only one element in the generated tuple changes from one iteration + to the next. + + >>> list(gray_product('AB','CD')) + [('A', 'C'), ('B', 'C'), ('B', 'D'), ('A', 'D')] + + This function consumes all of the input iterables before producing output. + If any of the input iterables have fewer than two items, ``ValueError`` + is raised. + + For information on the algorithm, see + `this section `__ + of Donald Knuth's *The Art of Computer Programming*. + """ + all_iterables = tuple(tuple(x) for x in iterables) + iterable_count = len(all_iterables) + for iterable in all_iterables: + if len(iterable) < 2: + raise ValueError("each iterable must have two or more items") + + # This is based on "Algorithm H" from section 7.2.1.1, page 20. + # a holds the indexes of the source iterables for the n-tuple to be yielded + # f is the array of "focus pointers" + # o is the array of "directions" + a = [0] * iterable_count + f = list(range(iterable_count + 1)) + o = [1] * iterable_count + while True: + yield tuple(all_iterables[i][a[i]] for i in range(iterable_count)) + j = f[0] + f[0] = 0 + if j == iterable_count: + break + a[j] = a[j] + o[j] + if a[j] == 0 or a[j] == len(all_iterables[j]) - 1: + o[j] = -o[j] + f[j] = f[j + 1] + f[j + 1] = j + 1 + + +def partial_product(*iterables): + """Yields tuples containing one item from each iterator, with subsequent + tuples changing a single item at a time by advancing each iterator until it + is exhausted. This sequence guarantees every value in each iterable is + output at least once without generating all possible combinations. + + This may be useful, for example, when testing an expensive function. + + >>> list(partial_product('AB', 'C', 'DEF')) + [('A', 'C', 'D'), ('B', 'C', 'D'), ('B', 'C', 'E'), ('B', 'C', 'F')] + """ + + iterators = list(map(iter, iterables)) + + try: + prod = [next(it) for it in iterators] + except StopIteration: + return + yield tuple(prod) + + for i, it in enumerate(iterators): + for prod[i] in it: + yield tuple(prod) + + +def takewhile_inclusive(predicate, iterable): + """A variant of :func:`takewhile` that yields one additional element. + + >>> list(takewhile_inclusive(lambda x: x < 5, [1, 4, 6, 4, 1])) + [1, 4, 6] + + :func:`takewhile` would return ``[1, 4]``. + """ + for x in iterable: + yield x + if not predicate(x): + break + + +def outer_product(func, xs, ys, *args, **kwargs): + """A generalized outer product that applies a binary function to all + pairs of items. Returns a 2D matrix with ``len(xs)`` rows and ``len(ys)`` + columns. + Also accepts ``*args`` and ``**kwargs`` that are passed to ``func``. + + Multiplication table: + + >>> list(outer_product(mul, range(1, 4), range(1, 6))) + [(1, 2, 3, 4, 5), (2, 4, 6, 8, 10), (3, 6, 9, 12, 15)] + + Cross tabulation: + + >>> xs = ['A', 'B', 'A', 'A', 'B', 'B', 'A', 'A', 'B', 'B'] + >>> ys = ['X', 'X', 'X', 'Y', 'Z', 'Z', 'Y', 'Y', 'Z', 'Z'] + >>> rows = list(zip(xs, ys)) + >>> count_rows = lambda x, y: rows.count((x, y)) + >>> list(outer_product(count_rows, sorted(set(xs)), sorted(set(ys)))) + [(2, 3, 0), (1, 0, 4)] + + Usage with ``*args`` and ``**kwargs``: + + >>> animals = ['cat', 'wolf', 'mouse'] + >>> list(outer_product(min, animals, animals, key=len)) + [('cat', 'cat', 'cat'), ('cat', 'wolf', 'wolf'), ('cat', 'wolf', 'mouse')] + """ + ys = tuple(ys) + return batched( + starmap(lambda x, y: func(x, y, *args, **kwargs), product(xs, ys)), + n=len(ys), + ) + + +def iter_suppress(iterable, *exceptions): + """Yield each of the items from *iterable*. If the iteration raises one of + the specified *exceptions*, that exception will be suppressed and iteration + will stop. + + >>> from itertools import chain + >>> def breaks_at_five(x): + ... while True: + ... if x >= 5: + ... raise RuntimeError + ... yield x + ... x += 1 + >>> it_1 = iter_suppress(breaks_at_five(1), RuntimeError) + >>> it_2 = iter_suppress(breaks_at_five(2), RuntimeError) + >>> list(chain(it_1, it_2)) + [1, 2, 3, 4, 2, 3, 4] + """ + try: + yield from iterable + except exceptions: + return + + +def filter_map(func, iterable): + """Apply *func* to every element of *iterable*, yielding only those which + are not ``None``. + + >>> elems = ['1', 'a', '2', 'b', '3'] + >>> list(filter_map(lambda s: int(s) if s.isnumeric() else None, elems)) + [1, 2, 3] + """ + for x in iterable: + y = func(x) + if y is not None: + yield y diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.pyi new file mode 100644 index 0000000000000000000000000000000000000000..9a5fc911a3ed7249b95cbda2433924b6aced2ae7 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/more.pyi @@ -0,0 +1,695 @@ +"""Stubs for more_itertools.more""" +from __future__ import annotations + +from types import TracebackType +from typing import ( + Any, + Callable, + Container, + ContextManager, + Generic, + Hashable, + Iterable, + Iterator, + overload, + Reversible, + Sequence, + Sized, + Type, + TypeVar, + type_check_only, +) +from typing_extensions import Protocol + +# Type and type variable definitions +_T = TypeVar('_T') +_T1 = TypeVar('_T1') +_T2 = TypeVar('_T2') +_U = TypeVar('_U') +_V = TypeVar('_V') +_W = TypeVar('_W') +_T_co = TypeVar('_T_co', covariant=True) +_GenFn = TypeVar('_GenFn', bound=Callable[..., Iterator[Any]]) +_Raisable = BaseException | Type[BaseException] + +@type_check_only +class _SizedIterable(Protocol[_T_co], Sized, Iterable[_T_co]): ... + +@type_check_only +class _SizedReversible(Protocol[_T_co], Sized, Reversible[_T_co]): ... + +@type_check_only +class _SupportsSlicing(Protocol[_T_co]): + def __getitem__(self, __k: slice) -> _T_co: ... + +def chunked( + iterable: Iterable[_T], n: int | None, strict: bool = ... +) -> Iterator[list[_T]]: ... +@overload +def first(iterable: Iterable[_T]) -> _T: ... +@overload +def first(iterable: Iterable[_T], default: _U) -> _T | _U: ... +@overload +def last(iterable: Iterable[_T]) -> _T: ... +@overload +def last(iterable: Iterable[_T], default: _U) -> _T | _U: ... +@overload +def nth_or_last(iterable: Iterable[_T], n: int) -> _T: ... +@overload +def nth_or_last(iterable: Iterable[_T], n: int, default: _U) -> _T | _U: ... + +class peekable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> peekable[_T]: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> _T | _U: ... + def prepend(self, *items: _T) -> None: ... + def __next__(self) -> _T: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> list[_T]: ... + +def consumer(func: _GenFn) -> _GenFn: ... +def ilen(iterable: Iterable[_T]) -> int: ... +def iterate(func: Callable[[_T], _T], start: _T) -> Iterator[_T]: ... +def with_iter( + context_manager: ContextManager[Iterable[_T]], +) -> Iterator[_T]: ... +def one( + iterable: Iterable[_T], + too_short: _Raisable | None = ..., + too_long: _Raisable | None = ..., +) -> _T: ... +def raise_(exception: _Raisable, *args: Any) -> None: ... +def strictly_n( + iterable: Iterable[_T], + n: int, + too_short: _GenFn | None = ..., + too_long: _GenFn | None = ..., +) -> list[_T]: ... +def distinct_permutations( + iterable: Iterable[_T], r: int | None = ... +) -> Iterator[tuple[_T, ...]]: ... +def intersperse( + e: _U, iterable: Iterable[_T], n: int = ... +) -> Iterator[_T | _U]: ... +def unique_to_each(*iterables: Iterable[_T]) -> list[list[_T]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, *, step: int = ... +) -> Iterator[tuple[_T | None, ...]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, fillvalue: _U, step: int = ... +) -> Iterator[tuple[_T | _U, ...]]: ... +def substrings(iterable: Iterable[_T]) -> Iterator[tuple[_T, ...]]: ... +def substrings_indexes( + seq: Sequence[_T], reverse: bool = ... +) -> Iterator[tuple[Sequence[_T], int, int]]: ... + +class bucket(Generic[_T, _U], Container[_U]): + def __init__( + self, + iterable: Iterable[_T], + key: Callable[[_T], _U], + validator: Callable[[_U], object] | None = ..., + ) -> None: ... + def __contains__(self, value: object) -> bool: ... + def __iter__(self) -> Iterator[_U]: ... + def __getitem__(self, value: object) -> Iterator[_T]: ... + +def spy( + iterable: Iterable[_T], n: int = ... +) -> tuple[list[_T], Iterator[_T]]: ... +def interleave(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def interleave_longest(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def interleave_evenly( + iterables: list[Iterable[_T]], lengths: list[int] | None = ... +) -> Iterator[_T]: ... +def collapse( + iterable: Iterable[Any], + base_type: type | None = ..., + levels: int | None = ..., +) -> Iterator[Any]: ... +@overload +def side_effect( + func: Callable[[_T], object], + iterable: Iterable[_T], + chunk_size: None = ..., + before: Callable[[], object] | None = ..., + after: Callable[[], object] | None = ..., +) -> Iterator[_T]: ... +@overload +def side_effect( + func: Callable[[list[_T]], object], + iterable: Iterable[_T], + chunk_size: int, + before: Callable[[], object] | None = ..., + after: Callable[[], object] | None = ..., +) -> Iterator[_T]: ... +def sliced( + seq: _SupportsSlicing[_T], n: int, strict: bool = ... +) -> Iterator[_T]: ... +def split_at( + iterable: Iterable[_T], + pred: Callable[[_T], object], + maxsplit: int = ..., + keep_separator: bool = ..., +) -> Iterator[list[_T]]: ... +def split_before( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[list[_T]]: ... +def split_after( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[list[_T]]: ... +def split_when( + iterable: Iterable[_T], + pred: Callable[[_T, _T], object], + maxsplit: int = ..., +) -> Iterator[list[_T]]: ... +def split_into( + iterable: Iterable[_T], sizes: Iterable[int | None] +) -> Iterator[list[_T]]: ... +@overload +def padded( + iterable: Iterable[_T], + *, + n: int | None = ..., + next_multiple: bool = ..., +) -> Iterator[_T | None]: ... +@overload +def padded( + iterable: Iterable[_T], + fillvalue: _U, + n: int | None = ..., + next_multiple: bool = ..., +) -> Iterator[_T | _U]: ... +@overload +def repeat_last(iterable: Iterable[_T]) -> Iterator[_T]: ... +@overload +def repeat_last(iterable: Iterable[_T], default: _U) -> Iterator[_T | _U]: ... +def distribute(n: int, iterable: Iterable[_T]) -> list[Iterator[_T]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., +) -> Iterator[tuple[_T | None, ...]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., + fillvalue: _U = ..., +) -> Iterator[tuple[_T | _U, ...]]: ... + +class UnequalIterablesError(ValueError): + def __init__(self, details: tuple[int, int, int] | None = ...) -> None: ... + +@overload +def zip_equal(__iter1: Iterable[_T1]) -> Iterator[tuple[_T1]]: ... +@overload +def zip_equal( + __iter1: Iterable[_T1], __iter2: Iterable[_T2] +) -> Iterator[tuple[_T1, _T2]]: ... +@overload +def zip_equal( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], +) -> Iterator[tuple[_T, ...]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[tuple[_T1 | None]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[tuple[_T1 | None, _T2 | None]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[tuple[_T | None, ...]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[tuple[_T1 | _U]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[tuple[_T1 | _U, _T2 | _U]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[tuple[_T | _U, ...]]: ... +def sort_together( + iterables: Iterable[Iterable[_T]], + key_list: Iterable[int] = ..., + key: Callable[..., Any] | None = ..., + reverse: bool = ..., +) -> list[tuple[_T, ...]]: ... +def unzip(iterable: Iterable[Sequence[_T]]) -> tuple[Iterator[_T], ...]: ... +def divide(n: int, iterable: Iterable[_T]) -> list[Iterator[_T]]: ... +def always_iterable( + obj: object, + base_type: type | tuple[type | tuple[Any, ...], ...] | None = ..., +) -> Iterator[Any]: ... +def adjacent( + predicate: Callable[[_T], bool], + iterable: Iterable[_T], + distance: int = ..., +) -> Iterator[tuple[bool, _T]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None = None, + valuefunc: None = None, + reducefunc: None = None, +) -> Iterator[tuple[_T, Iterator[_T]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None, + reducefunc: None, +) -> Iterator[tuple[_U, Iterator[_T]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: Callable[[_T], _V], + reducefunc: None, +) -> Iterable[tuple[_T, Iterable[_V]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: None, +) -> Iterable[tuple[_U, Iterator[_V]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: None, + reducefunc: Callable[[Iterator[_T]], _W], +) -> Iterable[tuple[_T, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None, + reducefunc: Callable[[Iterator[_T]], _W], +) -> Iterable[tuple[_U, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[Iterable[_V]], _W], +) -> Iterable[tuple[_T, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[Iterable[_V]], _W], +) -> Iterable[tuple[_U, _W]]: ... + +class numeric_range(Generic[_T, _U], Sequence[_T], Hashable, Reversible[_T]): + @overload + def __init__(self, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T, __step: _U) -> None: ... + def __bool__(self) -> bool: ... + def __contains__(self, elem: object) -> bool: ... + def __eq__(self, other: object) -> bool: ... + @overload + def __getitem__(self, key: int) -> _T: ... + @overload + def __getitem__(self, key: slice) -> numeric_range[_T, _U]: ... + def __hash__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def __reduce__( + self, + ) -> tuple[Type[numeric_range[_T, _U]], tuple[_T, _T, _U]]: ... + def __repr__(self) -> str: ... + def __reversed__(self) -> Iterator[_T]: ... + def count(self, value: _T) -> int: ... + def index(self, value: _T) -> int: ... # type: ignore + +def count_cycle( + iterable: Iterable[_T], n: int | None = ... +) -> Iterable[tuple[int, _T]]: ... +def mark_ends( + iterable: Iterable[_T], +) -> Iterable[tuple[bool, bool, _T]]: ... +def locate( + iterable: Iterable[_T], + pred: Callable[..., Any] = ..., + window_size: int | None = ..., +) -> Iterator[int]: ... +def lstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def rstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def strip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... + +class islice_extended(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T], *args: int | None) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + def __getitem__(self, index: slice) -> islice_extended[_T]: ... + +def always_reversible(iterable: Iterable[_T]) -> Iterator[_T]: ... +def consecutive_groups( + iterable: Iterable[_T], ordering: Callable[[_T], int] = ... +) -> Iterator[Iterator[_T]]: ... +@overload +def difference( + iterable: Iterable[_T], + func: Callable[[_T, _T], _U] = ..., + *, + initial: None = ..., +) -> Iterator[_T | _U]: ... +@overload +def difference( + iterable: Iterable[_T], func: Callable[[_T, _T], _U] = ..., *, initial: _U +) -> Iterator[_U]: ... + +class SequenceView(Generic[_T], Sequence[_T]): + def __init__(self, target: Sequence[_T]) -> None: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> Sequence[_T]: ... + def __len__(self) -> int: ... + +class seekable(Generic[_T], Iterator[_T]): + def __init__( + self, iterable: Iterable[_T], maxlen: int | None = ... + ) -> None: ... + def __iter__(self) -> seekable[_T]: ... + def __next__(self) -> _T: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> _T | _U: ... + def elements(self) -> SequenceView[_T]: ... + def seek(self, index: int) -> None: ... + def relative_seek(self, count: int) -> None: ... + +class run_length: + @staticmethod + def encode(iterable: Iterable[_T]) -> Iterator[tuple[_T, int]]: ... + @staticmethod + def decode(iterable: Iterable[tuple[_T, int]]) -> Iterator[_T]: ... + +def exactly_n( + iterable: Iterable[_T], n: int, predicate: Callable[[_T], object] = ... +) -> bool: ... +def circular_shifts(iterable: Iterable[_T]) -> list[tuple[_T, ...]]: ... +def make_decorator( + wrapping_func: Callable[..., _U], result_index: int = ... +) -> Callable[..., Callable[[Callable[..., Any]], Callable[..., _U]]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: None = ..., +) -> dict[_U, list[_T]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: None = ..., +) -> dict[_U, list[_V]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: Callable[[list[_T]], _W] = ..., +) -> dict[_U, _W]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[list[_V]], _W], +) -> dict[_U, _W]: ... +def rlocate( + iterable: Iterable[_T], + pred: Callable[..., object] = ..., + window_size: int | None = ..., +) -> Iterator[int]: ... +def replace( + iterable: Iterable[_T], + pred: Callable[..., object], + substitutes: Iterable[_U], + count: int | None = ..., + window_size: int = ..., +) -> Iterator[_T | _U]: ... +def partitions(iterable: Iterable[_T]) -> Iterator[list[list[_T]]]: ... +def set_partitions( + iterable: Iterable[_T], k: int | None = ... +) -> Iterator[list[list[_T]]]: ... + +class time_limited(Generic[_T], Iterator[_T]): + def __init__( + self, limit_seconds: float, iterable: Iterable[_T] + ) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + +@overload +def only( + iterable: Iterable[_T], *, too_long: _Raisable | None = ... +) -> _T | None: ... +@overload +def only( + iterable: Iterable[_T], default: _U, too_long: _Raisable | None = ... +) -> _T | _U: ... +def ichunked(iterable: Iterable[_T], n: int) -> Iterator[Iterator[_T]]: ... +def distinct_combinations( + iterable: Iterable[_T], r: int +) -> Iterator[tuple[_T, ...]]: ... +def filter_except( + validator: Callable[[Any], object], + iterable: Iterable[_T], + *exceptions: Type[BaseException], +) -> Iterator[_T]: ... +def map_except( + function: Callable[[Any], _U], + iterable: Iterable[_T], + *exceptions: Type[BaseException], +) -> Iterator[_U]: ... +def map_if( + iterable: Iterable[Any], + pred: Callable[[Any], bool], + func: Callable[[Any], Any], + func_else: Callable[[Any], Any] | None = ..., +) -> Iterator[Any]: ... +def sample( + iterable: Iterable[_T], + k: int, + weights: Iterable[float] | None = ..., +) -> list[_T]: ... +def is_sorted( + iterable: Iterable[_T], + key: Callable[[_T], _U] | None = ..., + reverse: bool = False, + strict: bool = False, +) -> bool: ... + +class AbortThread(BaseException): + pass + +class callback_iter(Generic[_T], Iterator[_T]): + def __init__( + self, + func: Callable[..., Any], + callback_kwd: str = ..., + wait_seconds: float = ..., + ) -> None: ... + def __enter__(self) -> callback_iter[_T]: ... + def __exit__( + self, + exc_type: Type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> bool | None: ... + def __iter__(self) -> callback_iter[_T]: ... + def __next__(self) -> _T: ... + def _reader(self) -> Iterator[_T]: ... + @property + def done(self) -> bool: ... + @property + def result(self) -> Any: ... + +def windowed_complete( + iterable: Iterable[_T], n: int +) -> Iterator[tuple[_T, ...]]: ... +def all_unique( + iterable: Iterable[_T], key: Callable[[_T], _U] | None = ... +) -> bool: ... +def nth_product(index: int, *args: Iterable[_T]) -> tuple[_T, ...]: ... +def nth_combination_with_replacement( + iterable: Iterable[_T], r: int, index: int +) -> tuple[_T, ...]: ... +def nth_permutation( + iterable: Iterable[_T], r: int, index: int +) -> tuple[_T, ...]: ... +def value_chain(*args: _T | Iterable[_T]) -> Iterable[_T]: ... +def product_index(element: Iterable[_T], *args: Iterable[_T]) -> int: ... +def combination_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def combination_with_replacement_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def permutation_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def repeat_each(iterable: Iterable[_T], n: int = ...) -> Iterator[_T]: ... + +class countable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> countable[_T]: ... + def __next__(self) -> _T: ... + +def chunked_even(iterable: Iterable[_T], n: int) -> Iterator[list[_T]]: ... +def zip_broadcast( + *objects: _T | Iterable[_T], + scalar_types: type | tuple[type | tuple[Any, ...], ...] | None = ..., + strict: bool = ..., +) -> Iterable[tuple[_T, ...]]: ... +def unique_in_window( + iterable: Iterable[_T], n: int, key: Callable[[_T], _U] | None = ... +) -> Iterator[_T]: ... +def duplicates_everseen( + iterable: Iterable[_T], key: Callable[[_T], _U] | None = ... +) -> Iterator[_T]: ... +def duplicates_justseen( + iterable: Iterable[_T], key: Callable[[_T], _U] | None = ... +) -> Iterator[_T]: ... +def classify_unique( + iterable: Iterable[_T], key: Callable[[_T], _U] | None = ... +) -> Iterator[tuple[_T, bool, bool]]: ... + +class _SupportsLessThan(Protocol): + def __lt__(self, __other: Any) -> bool: ... + +_SupportsLessThanT = TypeVar("_SupportsLessThanT", bound=_SupportsLessThan) + +@overload +def minmax( + iterable_or_value: Iterable[_SupportsLessThanT], *, key: None = None +) -> tuple[_SupportsLessThanT, _SupportsLessThanT]: ... +@overload +def minmax( + iterable_or_value: Iterable[_T], *, key: Callable[[_T], _SupportsLessThan] +) -> tuple[_T, _T]: ... +@overload +def minmax( + iterable_or_value: Iterable[_SupportsLessThanT], + *, + key: None = None, + default: _U, +) -> _U | tuple[_SupportsLessThanT, _SupportsLessThanT]: ... +@overload +def minmax( + iterable_or_value: Iterable[_T], + *, + key: Callable[[_T], _SupportsLessThan], + default: _U, +) -> _U | tuple[_T, _T]: ... +@overload +def minmax( + iterable_or_value: _SupportsLessThanT, + __other: _SupportsLessThanT, + *others: _SupportsLessThanT, +) -> tuple[_SupportsLessThanT, _SupportsLessThanT]: ... +@overload +def minmax( + iterable_or_value: _T, + __other: _T, + *others: _T, + key: Callable[[_T], _SupportsLessThan], +) -> tuple[_T, _T]: ... +def longest_common_prefix( + iterables: Iterable[Iterable[_T]], +) -> Iterator[_T]: ... +def iequals(*iterables: Iterable[Any]) -> bool: ... +def constrained_batches( + iterable: Iterable[_T], + max_size: int, + max_count: int | None = ..., + get_len: Callable[[_T], object] = ..., + strict: bool = ..., +) -> Iterator[tuple[_T]]: ... +def gray_product(*iterables: Iterable[_T]) -> Iterator[tuple[_T, ...]]: ... +def partial_product(*iterables: Iterable[_T]) -> Iterator[tuple[_T, ...]]: ... +def takewhile_inclusive( + predicate: Callable[[_T], bool], iterable: Iterable[_T] +) -> Iterator[_T]: ... +def outer_product( + func: Callable[[_T, _U], _V], + xs: Iterable[_T], + ys: Iterable[_U], + *args: Any, + **kwargs: Any, +) -> Iterator[tuple[_V, ...]]: ... +def iter_suppress( + iterable: Iterable[_T], + *exceptions: Type[BaseException], +) -> Iterator[_T]: ... +def filter_map( + func: Callable[[_T], _V | None], + iterable: Iterable[_T], +) -> Iterator[_V]: ... diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/py.typed b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.py new file mode 100644 index 0000000000000000000000000000000000000000..145e3cb5bd6bd5b916e3544e4b042a7ed203621a --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.py @@ -0,0 +1,1012 @@ +"""Imported from the recipes section of the itertools documentation. + +All functions taken from the recipes section of the itertools library docs +[1]_. +Some backward-compatible usability improvements have been made. + +.. [1] http://docs.python.org/library/itertools.html#recipes + +""" +import math +import operator + +from collections import deque +from collections.abc import Sized +from functools import partial, reduce +from itertools import ( + chain, + combinations, + compress, + count, + cycle, + groupby, + islice, + product, + repeat, + starmap, + tee, + zip_longest, +) +from random import randrange, sample, choice +from sys import hexversion + +__all__ = [ + 'all_equal', + 'batched', + 'before_and_after', + 'consume', + 'convolve', + 'dotproduct', + 'first_true', + 'factor', + 'flatten', + 'grouper', + 'iter_except', + 'iter_index', + 'matmul', + 'ncycles', + 'nth', + 'nth_combination', + 'padnone', + 'pad_none', + 'pairwise', + 'partition', + 'polynomial_eval', + 'polynomial_from_roots', + 'polynomial_derivative', + 'powerset', + 'prepend', + 'quantify', + 'reshape', + 'random_combination_with_replacement', + 'random_combination', + 'random_permutation', + 'random_product', + 'repeatfunc', + 'roundrobin', + 'sieve', + 'sliding_window', + 'subslices', + 'sum_of_squares', + 'tabulate', + 'tail', + 'take', + 'totient', + 'transpose', + 'triplewise', + 'unique_everseen', + 'unique_justseen', +] + +_marker = object() + + +# zip with strict is available for Python 3.10+ +try: + zip(strict=True) +except TypeError: + _zip_strict = zip +else: + _zip_strict = partial(zip, strict=True) + +# math.sumprod is available for Python 3.12+ +_sumprod = getattr(math, 'sumprod', lambda x, y: dotproduct(x, y)) + + +def take(n, iterable): + """Return first *n* items of the iterable as a list. + + >>> take(3, range(10)) + [0, 1, 2] + + If there are fewer than *n* items in the iterable, all of them are + returned. + + >>> take(10, range(3)) + [0, 1, 2] + + """ + return list(islice(iterable, n)) + + +def tabulate(function, start=0): + """Return an iterator over the results of ``func(start)``, + ``func(start + 1)``, ``func(start + 2)``... + + *func* should be a function that accepts one integer argument. + + If *start* is not specified it defaults to 0. It will be incremented each + time the iterator is advanced. + + >>> square = lambda x: x ** 2 + >>> iterator = tabulate(square, -3) + >>> take(4, iterator) + [9, 4, 1, 0] + + """ + return map(function, count(start)) + + +def tail(n, iterable): + """Return an iterator over the last *n* items of *iterable*. + + >>> t = tail(3, 'ABCDEFG') + >>> list(t) + ['E', 'F', 'G'] + + """ + # If the given iterable has a length, then we can use islice to get its + # final elements. Note that if the iterable is not actually Iterable, + # either islice or deque will throw a TypeError. This is why we don't + # check if it is Iterable. + if isinstance(iterable, Sized): + yield from islice(iterable, max(0, len(iterable) - n), None) + else: + yield from iter(deque(iterable, maxlen=n)) + + +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) + + +def nth(iterable, n, default=None): + """Returns the nth item or a default value. + + >>> l = range(10) + >>> nth(l, 3) + 3 + >>> nth(l, 20, "zebra") + 'zebra' + + """ + return next(islice(iterable, n, None), default) + + +def all_equal(iterable): + """ + Returns ``True`` if all the elements are equal to each other. + + >>> all_equal('aaaa') + True + >>> all_equal('aaab') + False + + """ + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def quantify(iterable, pred=bool): + """Return the how many times the predicate is true. + + >>> quantify([True, False, True]) + 2 + + """ + return sum(map(pred, iterable)) + + +def pad_none(iterable): + """Returns the sequence of elements and then returns ``None`` indefinitely. + + >>> take(5, pad_none(range(3))) + [0, 1, 2, None, None] + + Useful for emulating the behavior of the built-in :func:`map` function. + + See also :func:`padded`. + + """ + return chain(iterable, repeat(None)) + + +padnone = pad_none + + +def ncycles(iterable, n): + """Returns the sequence elements *n* times + + >>> list(ncycles(["a", "b"], 3)) + ['a', 'b', 'a', 'b', 'a', 'b'] + + """ + return chain.from_iterable(repeat(tuple(iterable), n)) + + +def dotproduct(vec1, vec2): + """Returns the dot product of the two iterables. + + >>> dotproduct([10, 10], [20, 20]) + 400 + + """ + return sum(map(operator.mul, vec1, vec2)) + + +def flatten(listOfLists): + """Return an iterator flattening one level of nesting in a list of lists. + + >>> list(flatten([[0, 1], [2, 3]])) + [0, 1, 2, 3] + + See also :func:`collapse`, which can flatten multiple levels of nesting. + + """ + return chain.from_iterable(listOfLists) + + +def repeatfunc(func, times=None, *args): + """Call *func* with *args* repeatedly, returning an iterable over the + results. + + If *times* is specified, the iterable will terminate after that many + repetitions: + + >>> from operator import add + >>> times = 4 + >>> args = 3, 5 + >>> list(repeatfunc(add, times, *args)) + [8, 8, 8, 8] + + If *times* is ``None`` the iterable will not terminate: + + >>> from random import randrange + >>> times = None + >>> args = 1, 11 + >>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP + [2, 4, 8, 1, 8, 4] + + """ + if times is None: + return starmap(func, repeat(args)) + return starmap(func, repeat(args, times)) + + +def _pairwise(iterable): + """Returns an iterator of paired items, overlapping, from the original + + >>> take(4, pairwise(count())) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + On Python 3.10 and above, this is an alias for :func:`itertools.pairwise`. + + """ + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + +try: + from itertools import pairwise as itertools_pairwise +except ImportError: + pairwise = _pairwise +else: + + def pairwise(iterable): + return itertools_pairwise(iterable) + + pairwise.__doc__ = _pairwise.__doc__ + + +class UnequalIterablesError(ValueError): + def __init__(self, details=None): + msg = 'Iterables have different lengths' + if details is not None: + msg += (': index 0 has length {}; index {} has length {}').format( + *details + ) + + super().__init__(msg) + + +def _zip_equal_generator(iterables): + for combo in zip_longest(*iterables, fillvalue=_marker): + for val in combo: + if val is _marker: + raise UnequalIterablesError() + yield combo + + +def _zip_equal(*iterables): + # Check whether the iterables are all the same size. + try: + first_size = len(iterables[0]) + for i, it in enumerate(iterables[1:], 1): + size = len(it) + if size != first_size: + raise UnequalIterablesError(details=(first_size, i, size)) + # All sizes are equal, we can use the built-in zip. + return zip(*iterables) + # If any one of the iterables didn't have a length, start reading + # them until one runs out. + except TypeError: + return _zip_equal_generator(iterables) + + +def grouper(iterable, n, incomplete='fill', fillvalue=None): + """Group elements from *iterable* into fixed-length groups of length *n*. + + >>> list(grouper('ABCDEF', 3)) + [('A', 'B', 'C'), ('D', 'E', 'F')] + + The keyword arguments *incomplete* and *fillvalue* control what happens for + iterables whose length is not a multiple of *n*. + + When *incomplete* is `'fill'`, the last group will contain instances of + *fillvalue*. + + >>> list(grouper('ABCDEFG', 3, incomplete='fill', fillvalue='x')) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')] + + When *incomplete* is `'ignore'`, the last group will not be emitted. + + >>> list(grouper('ABCDEFG', 3, incomplete='ignore', fillvalue='x')) + [('A', 'B', 'C'), ('D', 'E', 'F')] + + When *incomplete* is `'strict'`, a subclass of `ValueError` will be raised. + + >>> it = grouper('ABCDEFG', 3, incomplete='strict') + >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnequalIterablesError + + """ + args = [iter(iterable)] * n + if incomplete == 'fill': + return zip_longest(*args, fillvalue=fillvalue) + if incomplete == 'strict': + return _zip_equal(*args) + if incomplete == 'ignore': + return zip(*args) + else: + raise ValueError('Expected fill, strict, or ignore') + + +def roundrobin(*iterables): + """Yields an item from each iterable, alternating between them. + + >>> list(roundrobin('ABC', 'D', 'EF')) + ['A', 'D', 'E', 'B', 'F', 'C'] + + This function produces the same output as :func:`interleave_longest`, but + may perform better for some inputs (in particular when the number of + iterables is small). + + """ + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).__next__ for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +def partition(pred, iterable): + """ + Returns a 2-tuple of iterables derived from the input iterable. + The first yields the items that have ``pred(item) == False``. + The second yields the items that have ``pred(item) == True``. + + >>> is_odd = lambda x: x % 2 != 0 + >>> iterable = range(10) + >>> even_items, odd_items = partition(is_odd, iterable) + >>> list(even_items), list(odd_items) + ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) + + If *pred* is None, :func:`bool` is used. + + >>> iterable = [0, 1, False, True, '', ' '] + >>> false_items, true_items = partition(None, iterable) + >>> list(false_items), list(true_items) + ([0, False, ''], [1, True, ' ']) + + """ + if pred is None: + pred = bool + + t1, t2, p = tee(iterable, 3) + p1, p2 = tee(map(pred, p)) + return (compress(t1, map(operator.not_, p1)), compress(t2, p2)) + + +def powerset(iterable): + """Yields all possible subsets of the iterable. + + >>> list(powerset([1, 2, 3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + :func:`powerset` will operate on iterables that aren't :class:`set` + instances, so repeated elements in the input will produce repeated elements + in the output. Use :func:`unique_everseen` on the input to avoid generating + duplicates: + + >>> seq = [1, 1, 0] + >>> list(powerset(seq)) + [(), (1,), (1,), (0,), (1, 1), (1, 0), (1, 0), (1, 1, 0)] + >>> from more_itertools import unique_everseen + >>> list(powerset(unique_everseen(seq))) + [(), (1,), (0,), (1, 0)] + + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def unique_everseen(iterable, key=None): + """ + Yield unique elements, preserving order. + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + Sequences with a mix of hashable and unhashable items can be used. + The function will be slower (i.e., `O(n^2)`) for unhashable items. + + Remember that ``list`` objects are unhashable - you can use the *key* + parameter to transform the list to a tuple (which is hashable) to + avoid a slowdown. + + >>> iterable = ([1, 2], [2, 3], [1, 2]) + >>> list(unique_everseen(iterable)) # Slow + [[1, 2], [2, 3]] + >>> list(unique_everseen(iterable, key=tuple)) # Faster + [[1, 2], [2, 3]] + + Similarly, you may want to convert unhashable ``set`` objects with + ``key=frozenset``. For ``dict`` objects, + ``key=lambda x: frozenset(x.items())`` can be used. + + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seenset: + seenset_add(k) + yield element + except TypeError: + if k not in seenlist: + seenlist_add(k) + yield element + + +def unique_justseen(iterable, key=None): + """Yields elements in order, ignoring serial duplicates + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + """ + if key is None: + return map(operator.itemgetter(0), groupby(iterable)) + + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + + +def iter_except(func, exception, first=None): + """Yields results from a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel + to end the loop. + + >>> l = [0, 1, 2] + >>> list(iter_except(l.pop, IndexError)) + [2, 1, 0] + + Multiple exceptions can be specified as a stopping condition: + + >>> l = [1, 2, 3, '...', 4, 5, 6] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [7, 6, 5] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [4, 3, 2] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [] + + """ + try: + if first is not None: + yield first() + while 1: + yield func() + except exception: + pass + + +def first_true(iterable, default=None, pred=None): + """ + Returns the first true value in the iterable. + + If no true value is found, returns *default* + + If *pred* is not None, returns the first item for which + ``pred(item) == True`` . + + >>> first_true(range(10)) + 1 + >>> first_true(range(10), pred=lambda x: x > 5) + 6 + >>> first_true(range(10), default='missing', pred=lambda x: x > 9) + 'missing' + + """ + return next(filter(pred, iterable), default) + + +def random_product(*args, repeat=1): + """Draw an item at random from each of the input iterables. + + >>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP + ('c', 3, 'Z') + + If *repeat* is provided as a keyword argument, that many items will be + drawn from each iterable. + + >>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP + ('a', 2, 'd', 3) + + This equivalent to taking a random selection from + ``itertools.product(*args, **kwarg)``. + + """ + pools = [tuple(pool) for pool in args] * repeat + return tuple(choice(pool) for pool in pools) + + +def random_permutation(iterable, r=None): + """Return a random *r* length permutation of the elements in *iterable*. + + If *r* is not specified or is ``None``, then *r* defaults to the length of + *iterable*. + + >>> random_permutation(range(5)) # doctest:+SKIP + (3, 4, 0, 1, 2) + + This equivalent to taking a random selection from + ``itertools.permutations(iterable, r)``. + + """ + pool = tuple(iterable) + r = len(pool) if r is None else r + return tuple(sample(pool, r)) + + +def random_combination(iterable, r): + """Return a random *r* length subsequence of the elements in *iterable*. + + >>> random_combination(range(5), 3) # doctest:+SKIP + (2, 3, 4) + + This equivalent to taking a random selection from + ``itertools.combinations(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(sample(range(n), r)) + return tuple(pool[i] for i in indices) + + +def random_combination_with_replacement(iterable, r): + """Return a random *r* length subsequence of elements in *iterable*, + allowing individual elements to be repeated. + + >>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP + (0, 0, 1, 2, 2) + + This equivalent to taking a random selection from + ``itertools.combinations_with_replacement(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(randrange(n) for i in range(r)) + return tuple(pool[i] for i in indices) + + +def nth_combination(iterable, r, index): + """Equivalent to ``list(combinations(iterable, r))[index]``. + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`nth_combination` computes the subsequence at + sort position *index* directly, without computing the previous + subsequences. + + >>> nth_combination(range(5), 3, 5) + (0, 3, 4) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = 1 + k = min(r, n - r) + for i in range(1, k + 1): + c = c * (n - k + i) // i + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + while r: + c, n, r = c * r // n, n - 1, r - 1 + while index >= c: + index -= c + c, n = c * (n - r) // n, n - 1 + result.append(pool[-1 - n]) + + return tuple(result) + + +def prepend(value, iterator): + """Yield *value*, followed by the elements in *iterator*. + + >>> value = '0' + >>> iterator = ['1', '2', '3'] + >>> list(prepend(value, iterator)) + ['0', '1', '2', '3'] + + To prepend multiple values, see :func:`itertools.chain` + or :func:`value_chain`. + + """ + return chain([value], iterator) + + +def convolve(signal, kernel): + """Convolve the iterable *signal* with the iterable *kernel*. + + >>> signal = (1, 2, 3, 4, 5) + >>> kernel = [3, 2, 1] + >>> list(convolve(signal, kernel)) + [3, 8, 14, 20, 26, 14, 5] + + Note: the input arguments are not interchangeable, as the *kernel* + is immediately consumed and stored. + + """ + # This implementation intentionally doesn't match the one in the itertools + # documentation. + kernel = tuple(kernel)[::-1] + n = len(kernel) + window = deque([0], maxlen=n) * n + for x in chain(signal, repeat(0, n - 1)): + window.append(x) + yield _sumprod(kernel, window) + + +def before_and_after(predicate, it): + """A variant of :func:`takewhile` that allows complete access to the + remainder of the iterator. + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) # takewhile() would lose the 'd' + 'dEfGhI' + + Note that the first iterator must be fully consumed before the second + iterator can generate valid results. + """ + it = iter(it) + transition = [] + + def true_iterator(): + for elem in it: + if predicate(elem): + yield elem + else: + transition.append(elem) + return + + # Note: this is different from itertools recipes to allow nesting + # before_and_after remainders into before_and_after again. See tests + # for an example. + remainder_iterator = chain(transition, it) + + return true_iterator(), remainder_iterator + + +def triplewise(iterable): + """Return overlapping triplets from *iterable*. + + >>> list(triplewise('ABCDE')) + [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E')] + + """ + for (a, _), (b, c) in pairwise(pairwise(iterable)): + yield a, b, c + + +def sliding_window(iterable, n): + """Return a sliding window of width *n* over *iterable*. + + >>> list(sliding_window(range(6), 4)) + [(0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5)] + + If *iterable* has fewer than *n* items, then nothing is yielded: + + >>> list(sliding_window(range(3), 4)) + [] + + For a variant with more features, see :func:`windowed`. + """ + it = iter(iterable) + window = deque(islice(it, n - 1), maxlen=n) + for x in it: + window.append(x) + yield tuple(window) + + +def subslices(iterable): + """Return all contiguous non-empty subslices of *iterable*. + + >>> list(subslices('ABC')) + [['A'], ['A', 'B'], ['A', 'B', 'C'], ['B'], ['B', 'C'], ['C']] + + This is similar to :func:`substrings`, but emits items in a different + order. + """ + seq = list(iterable) + slices = starmap(slice, combinations(range(len(seq) + 1), 2)) + return map(operator.getitem, repeat(seq), slices) + + +def polynomial_from_roots(roots): + """Compute a polynomial's coefficients from its roots. + + >>> roots = [5, -4, 3] # (x - 5) * (x + 4) * (x - 3) + >>> polynomial_from_roots(roots) # x^3 - 4 * x^2 - 17 * x + 60 + [1, -4, -17, 60] + """ + factors = zip(repeat(1), map(operator.neg, roots)) + return list(reduce(convolve, factors, [1])) + + +def iter_index(iterable, value, start=0, stop=None): + """Yield the index of each place in *iterable* that *value* occurs, + beginning with index *start* and ending before index *stop*. + + See :func:`locate` for a more general means of finding the indexes + associated with particular values. + + >>> list(iter_index('AABCADEAF', 'A')) + [0, 1, 4, 7] + >>> list(iter_index('AABCADEAF', 'A', 1)) # start index is inclusive + [1, 4, 7] + >>> list(iter_index('AABCADEAF', 'A', 1, 7)) # stop index is not inclusive + [1, 4] + """ + seq_index = getattr(iterable, 'index', None) + if seq_index is None: + # Slow path for general iterables + it = islice(iterable, start, stop) + for i, element in enumerate(it, start): + if element is value or element == value: + yield i + else: + # Fast path for sequences + stop = len(iterable) if stop is None else stop + i = start - 1 + try: + while True: + yield (i := seq_index(value, i + 1, stop)) + except ValueError: + pass + + +def sieve(n): + """Yield the primes less than n. + + >>> list(sieve(30)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + """ + if n > 2: + yield 2 + start = 3 + data = bytearray((0, 1)) * (n // 2) + limit = math.isqrt(n) + 1 + for p in iter_index(data, 1, start, limit): + yield from iter_index(data, 1, start, p * p) + data[p * p : n : p + p] = bytes(len(range(p * p, n, p + p))) + start = p * p + yield from iter_index(data, 1, start) + + +def _batched(iterable, n, *, strict=False): + """Batch data into tuples of length *n*. If the number of items in + *iterable* is not divisible by *n*: + * The last batch will be shorter if *strict* is ``False``. + * :exc:`ValueError` will be raised if *strict* is ``True``. + + >>> list(batched('ABCDEFG', 3)) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)] + + On Python 3.13 and above, this is an alias for :func:`itertools.batched`. + """ + if n < 1: + raise ValueError('n must be at least one') + it = iter(iterable) + while batch := tuple(islice(it, n)): + if strict and len(batch) != n: + raise ValueError('batched(): incomplete batch') + yield batch + + +if hexversion >= 0x30D00A2: + from itertools import batched as itertools_batched + + def batched(iterable, n, *, strict=False): + return itertools_batched(iterable, n, strict=strict) + +else: + batched = _batched + + batched.__doc__ = _batched.__doc__ + + +def transpose(it): + """Swap the rows and columns of the input matrix. + + >>> list(transpose([(1, 2, 3), (11, 22, 33)])) + [(1, 11), (2, 22), (3, 33)] + + The caller should ensure that the dimensions of the input are compatible. + If the input is empty, no output will be produced. + """ + return _zip_strict(*it) + + +def reshape(matrix, cols): + """Reshape the 2-D input *matrix* to have a column count given by *cols*. + + >>> matrix = [(0, 1), (2, 3), (4, 5)] + >>> cols = 3 + >>> list(reshape(matrix, cols)) + [(0, 1, 2), (3, 4, 5)] + """ + return batched(chain.from_iterable(matrix), cols) + + +def matmul(m1, m2): + """Multiply two matrices. + + >>> list(matmul([(7, 5), (3, 5)], [(2, 5), (7, 9)])) + [(49, 80), (41, 60)] + + The caller should ensure that the dimensions of the input matrices are + compatible with each other. + """ + n = len(m2[0]) + return batched(starmap(_sumprod, product(m1, transpose(m2))), n) + + +def factor(n): + """Yield the prime factors of n. + + >>> list(factor(360)) + [2, 2, 2, 3, 3, 5] + """ + for prime in sieve(math.isqrt(n) + 1): + while not n % prime: + yield prime + n //= prime + if n == 1: + return + if n > 1: + yield n + + +def polynomial_eval(coefficients, x): + """Evaluate a polynomial at a specific value. + + Example: evaluating x^3 - 4 * x^2 - 17 * x + 60 at x = 2.5: + + >>> coefficients = [1, -4, -17, 60] + >>> x = 2.5 + >>> polynomial_eval(coefficients, x) + 8.125 + """ + n = len(coefficients) + if n == 0: + return x * 0 # coerce zero to the type of x + powers = map(pow, repeat(x), reversed(range(n))) + return _sumprod(coefficients, powers) + + +def sum_of_squares(it): + """Return the sum of the squares of the input values. + + >>> sum_of_squares([10, 20, 30]) + 1400 + """ + return _sumprod(*tee(it)) + + +def polynomial_derivative(coefficients): + """Compute the first derivative of a polynomial. + + Example: evaluating the derivative of x^3 - 4 * x^2 - 17 * x + 60 + + >>> coefficients = [1, -4, -17, 60] + >>> derivative_coefficients = polynomial_derivative(coefficients) + >>> derivative_coefficients + [3, -8, -17] + """ + n = len(coefficients) + powers = reversed(range(1, n)) + return list(map(operator.mul, coefficients, powers)) + + +def totient(n): + """Return the count of natural numbers up to *n* that are coprime with *n*. + + >>> totient(9) + 6 + >>> totient(12) + 4 + """ + for p in unique_justseen(factor(n)): + n = n // p * (p - 1) + + return n diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.pyi new file mode 100644 index 0000000000000000000000000000000000000000..ed4c19db49b6d9bd4905bcf4242f677987fc5a27 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/more_itertools/recipes.pyi @@ -0,0 +1,128 @@ +"""Stubs for more_itertools.recipes""" +from __future__ import annotations + +from typing import ( + Any, + Callable, + Iterable, + Iterator, + overload, + Sequence, + Type, + TypeVar, +) + +# Type and type variable definitions +_T = TypeVar('_T') +_T1 = TypeVar('_T1') +_T2 = TypeVar('_T2') +_U = TypeVar('_U') + +def take(n: int, iterable: Iterable[_T]) -> list[_T]: ... +def tabulate( + function: Callable[[int], _T], start: int = ... +) -> Iterator[_T]: ... +def tail(n: int, iterable: Iterable[_T]) -> Iterator[_T]: ... +def consume(iterator: Iterable[_T], n: int | None = ...) -> None: ... +@overload +def nth(iterable: Iterable[_T], n: int) -> _T | None: ... +@overload +def nth(iterable: Iterable[_T], n: int, default: _U) -> _T | _U: ... +def all_equal(iterable: Iterable[_T]) -> bool: ... +def quantify( + iterable: Iterable[_T], pred: Callable[[_T], bool] = ... +) -> int: ... +def pad_none(iterable: Iterable[_T]) -> Iterator[_T | None]: ... +def padnone(iterable: Iterable[_T]) -> Iterator[_T | None]: ... +def ncycles(iterable: Iterable[_T], n: int) -> Iterator[_T]: ... +def dotproduct(vec1: Iterable[_T1], vec2: Iterable[_T2]) -> Any: ... +def flatten(listOfLists: Iterable[Iterable[_T]]) -> Iterator[_T]: ... +def repeatfunc( + func: Callable[..., _U], times: int | None = ..., *args: Any +) -> Iterator[_U]: ... +def pairwise(iterable: Iterable[_T]) -> Iterator[tuple[_T, _T]]: ... +def grouper( + iterable: Iterable[_T], + n: int, + incomplete: str = ..., + fillvalue: _U = ..., +) -> Iterator[tuple[_T | _U, ...]]: ... +def roundrobin(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def partition( + pred: Callable[[_T], object] | None, iterable: Iterable[_T] +) -> tuple[Iterator[_T], Iterator[_T]]: ... +def powerset(iterable: Iterable[_T]) -> Iterator[tuple[_T, ...]]: ... +def unique_everseen( + iterable: Iterable[_T], key: Callable[[_T], _U] | None = ... +) -> Iterator[_T]: ... +def unique_justseen( + iterable: Iterable[_T], key: Callable[[_T], object] | None = ... +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], + exception: Type[BaseException] | tuple[Type[BaseException], ...], + first: None = ..., +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], + exception: Type[BaseException] | tuple[Type[BaseException], ...], + first: Callable[[], _U], +) -> Iterator[_T | _U]: ... +@overload +def first_true( + iterable: Iterable[_T], *, pred: Callable[[_T], object] | None = ... +) -> _T | None: ... +@overload +def first_true( + iterable: Iterable[_T], + default: _U, + pred: Callable[[_T], object] | None = ..., +) -> _T | _U: ... +def random_product( + *args: Iterable[_T], repeat: int = ... +) -> tuple[_T, ...]: ... +def random_permutation( + iterable: Iterable[_T], r: int | None = ... +) -> tuple[_T, ...]: ... +def random_combination(iterable: Iterable[_T], r: int) -> tuple[_T, ...]: ... +def random_combination_with_replacement( + iterable: Iterable[_T], r: int +) -> tuple[_T, ...]: ... +def nth_combination( + iterable: Iterable[_T], r: int, index: int +) -> tuple[_T, ...]: ... +def prepend(value: _T, iterator: Iterable[_U]) -> Iterator[_T | _U]: ... +def convolve(signal: Iterable[_T], kernel: Iterable[_T]) -> Iterator[_T]: ... +def before_and_after( + predicate: Callable[[_T], bool], it: Iterable[_T] +) -> tuple[Iterator[_T], Iterator[_T]]: ... +def triplewise(iterable: Iterable[_T]) -> Iterator[tuple[_T, _T, _T]]: ... +def sliding_window( + iterable: Iterable[_T], n: int +) -> Iterator[tuple[_T, ...]]: ... +def subslices(iterable: Iterable[_T]) -> Iterator[list[_T]]: ... +def polynomial_from_roots(roots: Sequence[_T]) -> list[_T]: ... +def iter_index( + iterable: Iterable[_T], + value: Any, + start: int | None = ..., + stop: int | None = ..., +) -> Iterator[int]: ... +def sieve(n: int) -> Iterator[int]: ... +def batched( + iterable: Iterable[_T], n: int, *, strict: bool = False +) -> Iterator[tuple[_T]]: ... +def transpose( + it: Iterable[Iterable[_T]], +) -> Iterator[tuple[_T, ...]]: ... +def reshape( + matrix: Iterable[Iterable[_T]], cols: int +) -> Iterator[tuple[_T, ...]]: ... +def matmul(m1: Sequence[_T], m2: Sequence[_T]) -> Iterator[tuple[_T]]: ... +def factor(n: int) -> Iterator[int]: ... +def polynomial_eval(coefficients: Sequence[_T], x: _U) -> _U: ... +def sum_of_squares(it: Iterable[_T]) -> _T: ... +def polynomial_derivative(coefficients: Sequence[_T]) -> list[_T]: ... +def totient(n: int) -> int: ... diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e7c0aa12ca950f230c8092436a985b2305702642 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__init__.py @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "24.0" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = "2014 %s" % __author__ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cb5e937ede0d20ca79ac7266593da9b934f0a9e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..647f67426acb8413ae700da3ba01304fa687eed8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..879a572fe3d8f35dee67e360c86c094b8205fa7d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6aca5f55d56f2b015ead4b3ac7554459d51cb7c Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_parser.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..515c6b6d6715a7a950baeb7475c80da2f13df3d4 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_parser.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7b0d865b95ad0ec2d57406d02ec46a24045447d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_structures.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cac3c4490c87ac65f81b46dc05094f42fc16da8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8f9e9a1bb31739545c9d536156ca69eb43d24cdb Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/markers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/metadata.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/metadata.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fc79dffb42b1e073d6c84c768fd09108f0223f0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/metadata.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d37ac3f8b31558604420b13a19c9afcf4dcac63 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/requirements.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a1977a15db6a5766b6ee0813b6ea4e961ce0afa0 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cecc6c1debf58bbca50d401398434baa368bacd9 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/tags.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d80c5bbd7f09c675f2edf4e985ace3d06d2c211 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/utils.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d50e3305fccdfb5f7a326a1414fbffd22013375 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/__pycache__/version.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_elffile.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_elffile.py new file mode 100644 index 0000000000000000000000000000000000000000..6fb19b30bb53c18f38a9ef02dd7c4478670fb962 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_elffile.py @@ -0,0 +1,108 @@ +""" +ELF file parser. + +This provides a class ``ELFFile`` that parses an ELF executable in a similar +interface to ``ZipFile``. Only the read interface is implemented. + +Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca +ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html +""" + +import enum +import os +import struct +from typing import IO, Optional, Tuple + + +class ELFInvalid(ValueError): + pass + + +class EIClass(enum.IntEnum): + C32 = 1 + C64 = 2 + + +class EIData(enum.IntEnum): + Lsb = 1 + Msb = 2 + + +class EMachine(enum.IntEnum): + I386 = 3 + S390 = 22 + Arm = 40 + X8664 = 62 + AArc64 = 183 + + +class ELFFile: + """ + Representation of an ELF executable. + """ + + def __init__(self, f: IO[bytes]) -> None: + self._f = f + + try: + ident = self._read("16B") + except struct.error: + raise ELFInvalid("unable to parse identification") + magic = bytes(ident[:4]) + if magic != b"\x7fELF": + raise ELFInvalid(f"invalid magic: {magic!r}") + + self.capacity = ident[4] # Format for program header (bitness). + self.encoding = ident[5] # Data structure encoding (endianness). + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, self._p_fmt, self._p_idx = { + (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. + (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. + }[(self.capacity, self.encoding)] + except KeyError: + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or " + f"encoding ({self.encoding})" + ) + + try: + ( + _, + self.machine, # Architecture type. + _, + _, + self._e_phoff, # Offset of program header. + _, + self.flags, # Processor-specific flags. + _, + self._e_phentsize, # Size of section. + self._e_phnum, # Number of sections. + ) = self._read(e_fmt) + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e + + def _read(self, fmt: str) -> Tuple[int, ...]: + return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) + + @property + def interpreter(self) -> Optional[str]: + """ + The path recorded in the ``PT_INTERP`` section header. + """ + for index in range(self._e_phnum): + self._f.seek(self._e_phoff + self._e_phentsize * index) + try: + data = self._read(self._p_fmt) + except struct.error: + continue + if data[self._p_idx[0]] != 3: # Not PT_INTERP. + continue + self._f.seek(data[self._p_idx[1]]) + return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") + return None diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_manylinux.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_manylinux.py new file mode 100644 index 0000000000000000000000000000000000000000..ad62505f3ff66c3d4da07ce1f2a50d9f10bc1bdd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_manylinux.py @@ -0,0 +1,260 @@ +import collections +import contextlib +import functools +import os +import re +import sys +import warnings +from typing import Dict, Generator, Iterator, NamedTuple, Optional, Sequence, Tuple + +from ._elffile import EIClass, EIData, ELFFile, EMachine + +EF_ARM_ABIMASK = 0xFF000000 +EF_ARM_ABI_VER5 = 0x05000000 +EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + +# `os.PathLike` not a generic type until Python 3.9, so sticking with `str` +# as the type for `path` until then. +@contextlib.contextmanager +def _parse_elf(path: str) -> Generator[Optional[ELFFile], None, None]: + try: + with open(path, "rb") as f: + yield ELFFile(f) + except (OSError, TypeError, ValueError): + yield None + + +def _is_linux_armhf(executable: str) -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.Arm + and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5 + and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD + ) + + +def _is_linux_i686(executable: str) -> bool: + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.I386 + ) + + +def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool: + if "armv7l" in archs: + return _is_linux_armhf(executable) + if "i686" in archs: + return _is_linux_i686(executable) + allowed_archs = { + "x86_64", + "aarch64", + "ppc64", + "ppc64le", + "s390x", + "loongarch64", + "riscv64", + } + return any(arch in allowed_archs for arch in archs) + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: Dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> Optional[str]: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # Should be a string like "glibc 2.17". + version_string: Optional[str] = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.rsplit() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> Optional[str]: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> Optional[str]: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> Tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + f"Expected glibc version with 2 components major.minor," + f" got: {version_str}", + RuntimeWarning, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache() +def _get_glibc_version() -> Tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate manylinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be manylinux-compatible. + + :returns: An iterator of compatible manylinux tags. + """ + if not _have_compatible_abi(sys.executable, archs): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if set(archs) & {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for arch in archs: + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(arch, glibc_version): + yield f"{tag}_{arch}" + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(arch, glibc_version): + yield f"{legacy_tag}_{arch}" diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_musllinux.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_musllinux.py new file mode 100644 index 0000000000000000000000000000000000000000..86419df9d7087f3f8b6d0096f32a52c24b05e7c1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_musllinux.py @@ -0,0 +1,83 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +import functools +import re +import subprocess +import sys +from typing import Iterator, NamedTuple, Optional, Sequence + +from ._elffile import ELFFile + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> Optional[_MuslVersion]: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache() +def _get_musl_version(executable: str) -> Optional[_MuslVersion]: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + try: + with open(executable, "rb") as f: + ld = ELFFile(f).interpreter + except (OSError, TypeError, ValueError): + return None + if ld is None or "musl" not in ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for arch in archs: + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_parser.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..684df75457cb82d3683dc99ff52c5bf911f3341b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_parser.py @@ -0,0 +1,356 @@ +"""Handwritten parser of dependency specifiers. + +The docstring for each __parse_* function contains ENBF-inspired grammar representing +the implementation. +""" + +import ast +from typing import Any, List, NamedTuple, Optional, Tuple, Union + +from ._tokenizer import DEFAULT_RULES, Tokenizer + + +class Node: + def __init__(self, value: str) -> None: + self.value = value + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" + + def serialize(self) -> str: + raise NotImplementedError + + +class Variable(Node): + def serialize(self) -> str: + return str(self) + + +class Value(Node): + def serialize(self) -> str: + return f'"{self}"' + + +class Op(Node): + def serialize(self) -> str: + return str(self) + + +MarkerVar = Union[Variable, Value] +MarkerItem = Tuple[MarkerVar, Op, MarkerVar] +# MarkerAtom = Union[MarkerItem, List["MarkerAtom"]] +# MarkerList = List[Union["MarkerList", MarkerAtom, str]] +# mypy does not support recursive type definition +# https://github.com/python/mypy/issues/731 +MarkerAtom = Any +MarkerList = List[Any] + + +class ParsedRequirement(NamedTuple): + name: str + url: str + extras: List[str] + specifier: str + marker: Optional[MarkerList] + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for dependency specifier +# -------------------------------------------------------------------------------------- +def parse_requirement(source: str) -> ParsedRequirement: + return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement: + """ + requirement = WS? IDENTIFIER WS? extras WS? requirement_details + """ + tokenizer.consume("WS") + + name_token = tokenizer.expect( + "IDENTIFIER", expected="package name at the start of dependency specifier" + ) + name = name_token.text + tokenizer.consume("WS") + + extras = _parse_extras(tokenizer) + tokenizer.consume("WS") + + url, specifier, marker = _parse_requirement_details(tokenizer) + tokenizer.expect("END", expected="end of dependency specifier") + + return ParsedRequirement(name, url, extras, specifier, marker) + + +def _parse_requirement_details( + tokenizer: Tokenizer, +) -> Tuple[str, str, Optional[MarkerList]]: + """ + requirement_details = AT URL (WS requirement_marker?)? + | specifier WS? (requirement_marker)? + """ + + specifier = "" + url = "" + marker = None + + if tokenizer.check("AT"): + tokenizer.read() + tokenizer.consume("WS") + + url_start = tokenizer.position + url = tokenizer.expect("URL", expected="URL after @").text + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + tokenizer.expect("WS", expected="whitespace after URL") + + # The input might end after whitespace. + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, span_start=url_start, after="URL and whitespace" + ) + else: + specifier_start = tokenizer.position + specifier = _parse_specifier(tokenizer) + tokenizer.consume("WS") + + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, + span_start=specifier_start, + after=( + "version specifier" + if specifier + else "name and no valid version specifier" + ), + ) + + return (url, specifier, marker) + + +def _parse_requirement_marker( + tokenizer: Tokenizer, *, span_start: int, after: str +) -> MarkerList: + """ + requirement_marker = SEMICOLON marker WS? + """ + + if not tokenizer.check("SEMICOLON"): + tokenizer.raise_syntax_error( + f"Expected end or semicolon (after {after})", + span_start=span_start, + ) + tokenizer.read() + + marker = _parse_marker(tokenizer) + tokenizer.consume("WS") + + return marker + + +def _parse_extras(tokenizer: Tokenizer) -> List[str]: + """ + extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)? + """ + if not tokenizer.check("LEFT_BRACKET", peek=True): + return [] + + with tokenizer.enclosing_tokens( + "LEFT_BRACKET", + "RIGHT_BRACKET", + around="extras", + ): + tokenizer.consume("WS") + extras = _parse_extras_list(tokenizer) + tokenizer.consume("WS") + + return extras + + +def _parse_extras_list(tokenizer: Tokenizer) -> List[str]: + """ + extras_list = identifier (wsp* ',' wsp* identifier)* + """ + extras: List[str] = [] + + if not tokenizer.check("IDENTIFIER"): + return extras + + extras.append(tokenizer.read().text) + + while True: + tokenizer.consume("WS") + if tokenizer.check("IDENTIFIER", peek=True): + tokenizer.raise_syntax_error("Expected comma between extra names") + elif not tokenizer.check("COMMA"): + break + + tokenizer.read() + tokenizer.consume("WS") + + extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma") + extras.append(extra_token.text) + + return extras + + +def _parse_specifier(tokenizer: Tokenizer) -> str: + """ + specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS + | WS? version_many WS? + """ + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="version specifier", + ): + tokenizer.consume("WS") + parsed_specifiers = _parse_version_many(tokenizer) + tokenizer.consume("WS") + + return parsed_specifiers + + +def _parse_version_many(tokenizer: Tokenizer) -> str: + """ + version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)? + """ + parsed_specifiers = "" + while tokenizer.check("SPECIFIER"): + span_start = tokenizer.position + parsed_specifiers += tokenizer.read().text + if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True): + tokenizer.raise_syntax_error( + ".* suffix can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position + 1, + ) + if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True): + tokenizer.raise_syntax_error( + "Local version label can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position, + ) + tokenizer.consume("WS") + if not tokenizer.check("COMMA"): + break + parsed_specifiers += tokenizer.read().text + tokenizer.consume("WS") + + return parsed_specifiers + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for marker expression +# -------------------------------------------------------------------------------------- +def parse_marker(source: str) -> MarkerList: + return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList: + retval = _parse_marker(tokenizer) + tokenizer.expect("END", expected="end of marker expression") + return retval + + +def _parse_marker(tokenizer: Tokenizer) -> MarkerList: + """ + marker = marker_atom (BOOLOP marker_atom)+ + """ + expression = [_parse_marker_atom(tokenizer)] + while tokenizer.check("BOOLOP"): + token = tokenizer.read() + expr_right = _parse_marker_atom(tokenizer) + expression.extend((token.text, expr_right)) + return expression + + +def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom: + """ + marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS? + | WS? marker_item WS? + """ + + tokenizer.consume("WS") + if tokenizer.check("LEFT_PARENTHESIS", peek=True): + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="marker expression", + ): + tokenizer.consume("WS") + marker: MarkerAtom = _parse_marker(tokenizer) + tokenizer.consume("WS") + else: + marker = _parse_marker_item(tokenizer) + tokenizer.consume("WS") + return marker + + +def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem: + """ + marker_item = WS? marker_var WS? marker_op WS? marker_var WS? + """ + tokenizer.consume("WS") + marker_var_left = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + marker_op = _parse_marker_op(tokenizer) + tokenizer.consume("WS") + marker_var_right = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + return (marker_var_left, marker_op, marker_var_right) + + +def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: + """ + marker_var = VARIABLE | QUOTED_STRING + """ + if tokenizer.check("VARIABLE"): + return process_env_var(tokenizer.read().text.replace(".", "_")) + elif tokenizer.check("QUOTED_STRING"): + return process_python_str(tokenizer.read().text) + else: + tokenizer.raise_syntax_error( + message="Expected a marker variable or quoted string" + ) + + +def process_env_var(env_var: str) -> Variable: + if env_var in ("platform_python_implementation", "python_implementation"): + return Variable("platform_python_implementation") + else: + return Variable(env_var) + + +def process_python_str(python_str: str) -> Value: + value = ast.literal_eval(python_str) + return Value(str(value)) + + +def _parse_marker_op(tokenizer: Tokenizer) -> Op: + """ + marker_op = IN | NOT IN | OP + """ + if tokenizer.check("IN"): + tokenizer.read() + return Op("in") + elif tokenizer.check("NOT"): + tokenizer.read() + tokenizer.expect("WS", expected="whitespace after 'not'") + tokenizer.expect("IN", expected="'in' after 'not'") + return Op("not in") + elif tokenizer.check("OP"): + return Op(tokenizer.read().text) + else: + return tokenizer.raise_syntax_error( + "Expected marker operator, one of " + "<=, <, !=, ==, >=, >, ~=, ===, in, not in" + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_structures.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_structures.py new file mode 100644 index 0000000000000000000000000000000000000000..90a6465f9682c886363eea5327dac64bf623a6ff --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_structures.py @@ -0,0 +1,61 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +class InfinityType: + def __repr__(self) -> str: + return "Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return False + + def __le__(self, other: object) -> bool: + return False + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return True + + def __ge__(self, other: object) -> bool: + return True + + def __neg__(self: object) -> "NegativeInfinityType": + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType: + def __repr__(self) -> str: + return "-Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return True + + def __le__(self, other: object) -> bool: + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return False + + def __ge__(self, other: object) -> bool: + return False + + def __neg__(self: object) -> InfinityType: + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_tokenizer.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_tokenizer.py new file mode 100644 index 0000000000000000000000000000000000000000..dd0d648d49a7c1a62d25ce5c9107aa448a8a22d1 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/_tokenizer.py @@ -0,0 +1,192 @@ +import contextlib +import re +from dataclasses import dataclass +from typing import Dict, Iterator, NoReturn, Optional, Tuple, Union + +from .specifiers import Specifier + + +@dataclass +class Token: + name: str + text: str + position: int + + +class ParserSyntaxError(Exception): + """The provided source text could not be parsed correctly.""" + + def __init__( + self, + message: str, + *, + source: str, + span: Tuple[int, int], + ) -> None: + self.span = span + self.message = message + self.source = source + + super().__init__() + + def __str__(self) -> str: + marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^" + return "\n ".join([self.message, self.source, marker]) + + +DEFAULT_RULES: "Dict[str, Union[str, re.Pattern[str]]]" = { + "LEFT_PARENTHESIS": r"\(", + "RIGHT_PARENTHESIS": r"\)", + "LEFT_BRACKET": r"\[", + "RIGHT_BRACKET": r"\]", + "SEMICOLON": r";", + "COMMA": r",", + "QUOTED_STRING": re.compile( + r""" + ( + ('[^']*') + | + ("[^"]*") + ) + """, + re.VERBOSE, + ), + "OP": r"(===|==|~=|!=|<=|>=|<|>)", + "BOOLOP": r"\b(or|and)\b", + "IN": r"\bin\b", + "NOT": r"\bnot\b", + "VARIABLE": re.compile( + r""" + \b( + python_version + |python_full_version + |os[._]name + |sys[._]platform + |platform_(release|system) + |platform[._](version|machine|python_implementation) + |python_implementation + |implementation_(name|version) + |extra + )\b + """, + re.VERBOSE, + ), + "SPECIFIER": re.compile( + Specifier._operator_regex_str + Specifier._version_regex_str, + re.VERBOSE | re.IGNORECASE, + ), + "AT": r"\@", + "URL": r"[^ \t]+", + "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b", + "VERSION_PREFIX_TRAIL": r"\.\*", + "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*", + "WS": r"[ \t]+", + "END": r"$", +} + + +class Tokenizer: + """Context-sensitive token parsing. + + Provides methods to examine the input stream to check whether the next token + matches. + """ + + def __init__( + self, + source: str, + *, + rules: "Dict[str, Union[str, re.Pattern[str]]]", + ) -> None: + self.source = source + self.rules: Dict[str, re.Pattern[str]] = { + name: re.compile(pattern) for name, pattern in rules.items() + } + self.next_token: Optional[Token] = None + self.position = 0 + + def consume(self, name: str) -> None: + """Move beyond provided token name, if at current position.""" + if self.check(name): + self.read() + + def check(self, name: str, *, peek: bool = False) -> bool: + """Check whether the next token has the provided name. + + By default, if the check succeeds, the token *must* be read before + another check. If `peek` is set to `True`, the token is not loaded and + would need to be checked again. + """ + assert ( + self.next_token is None + ), f"Cannot check for {name!r}, already have {self.next_token!r}" + assert name in self.rules, f"Unknown token name: {name!r}" + + expression = self.rules[name] + + match = expression.match(self.source, self.position) + if match is None: + return False + if not peek: + self.next_token = Token(name, match[0], self.position) + return True + + def expect(self, name: str, *, expected: str) -> Token: + """Expect a certain token name next, failing with a syntax error otherwise. + + The token is *not* read. + """ + if not self.check(name): + raise self.raise_syntax_error(f"Expected {expected}") + return self.read() + + def read(self) -> Token: + """Consume the next token and return it.""" + token = self.next_token + assert token is not None + + self.position += len(token.text) + self.next_token = None + + return token + + def raise_syntax_error( + self, + message: str, + *, + span_start: Optional[int] = None, + span_end: Optional[int] = None, + ) -> NoReturn: + """Raise ParserSyntaxError at the given position.""" + span = ( + self.position if span_start is None else span_start, + self.position if span_end is None else span_end, + ) + raise ParserSyntaxError( + message, + source=self.source, + span=span, + ) + + @contextlib.contextmanager + def enclosing_tokens( + self, open_token: str, close_token: str, *, around: str + ) -> Iterator[None]: + if self.check(open_token): + open_position = self.position + self.read() + else: + open_position = None + + yield + + if open_position is None: + return + + if not self.check(close_token): + self.raise_syntax_error( + f"Expected matching {close_token} for {open_token}, after {around}", + span_start=open_position, + ) + + self.read() diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/markers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/markers.py new file mode 100644 index 0000000000000000000000000000000000000000..8b98fca7233be6dd9324cd2b6d71b6a8ac91a6cb --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/markers.py @@ -0,0 +1,252 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import operator +import os +import platform +import sys +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from ._parser import ( + MarkerAtom, + MarkerList, + Op, + Value, + Variable, + parse_marker as _parse_marker, +) +from ._tokenizer import ParserSyntaxError +from .specifiers import InvalidSpecifier, Specifier +from .utils import canonicalize_name + +__all__ = [ + "InvalidMarker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "Marker", + "default_environment", +] + +Operator = Callable[[str, str], bool] + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +def _normalize_extra_values(results: Any) -> Any: + """ + Normalize extra values. + """ + if isinstance(results[0], tuple): + lhs, op, rhs = results[0] + if isinstance(lhs, Variable) and lhs.value == "extra": + normalized_extra = canonicalize_name(rhs.value) + rhs = Value(normalized_extra) + elif isinstance(rhs, Variable) and rhs.value == "extra": + normalized_extra = canonicalize_name(lhs.value) + lhs = Value(normalized_extra) + results[0] = lhs, op, rhs + return results + + +def _format_marker( + marker: Union[List[str], MarkerAtom, str], first: Optional[bool] = True +) -> str: + + assert isinstance(marker, (list, tuple, str)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators: Dict[str, Operator] = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs: str, op: Op, rhs: str) -> bool: + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs, prereleases=True) + + oper: Optional[Operator] = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") + + return oper(lhs, rhs) + + +def _normalize(*values: str, key: str) -> Tuple[str, ...]: + # PEP 685 – Comparison of extra names for optional distribution dependencies + # https://peps.python.org/pep-0685/ + # > When comparing extra names, tools MUST normalize the names being + # > compared using the semantics outlined in PEP 503 for names + if key == "extra": + return tuple(canonicalize_name(v) for v in values) + + # other environment markers don't have such standards + return values + + +def _evaluate_markers(markers: MarkerList, environment: Dict[str, str]) -> bool: + groups: List[List[bool]] = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, str)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + environment_key = lhs.value + lhs_value = environment[environment_key] + rhs_value = rhs.value + else: + lhs_value = lhs.value + environment_key = rhs.value + rhs_value = environment[environment_key] + + lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key) + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info: "sys._version_info") -> str: + version = "{0.major}.{0.minor}.{0.micro}".format(info) + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment() -> Dict[str, str]: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker: + def __init__(self, marker: str) -> None: + # Note: We create a Marker object without calling this constructor in + # packaging.requirements.Requirement. If any additional logic is + # added here, make sure to mirror/adapt Requirement. + try: + self._markers = _normalize_extra_values(_parse_marker(marker)) + # The attribute `_markers` can be described in terms of a recursive type: + # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]] + # + # For example, the following expression: + # python_version > "3.6" or (python_version == "3.6" and os_name == "unix") + # + # is parsed into: + # [ + # (, ')>, ), + # 'and', + # [ + # (, , ), + # 'or', + # (, , ) + # ] + # ] + except ParserSyntaxError as e: + raise InvalidMarker(str(e)) from e + + def __str__(self) -> str: + return _format_marker(self._markers) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash((self.__class__.__name__, str(self))) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Marker): + return NotImplemented + + return str(self) == str(other) + + def evaluate(self, environment: Optional[Dict[str, str]] = None) -> bool: + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. + + The environment is determined from the current Python process. + """ + current_environment = default_environment() + current_environment["extra"] = "" + if environment is not None: + current_environment.update(environment) + # The API used to allow setting extra to None. We need to handle this + # case for backwards compatibility. + if current_environment["extra"] is None: + current_environment["extra"] = "" + + return _evaluate_markers(self._markers, current_environment) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/metadata.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..fb274930799da0f8ee17566b5b587b4047282c7b --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/metadata.py @@ -0,0 +1,825 @@ +import email.feedparser +import email.header +import email.message +import email.parser +import email.policy +import sys +import typing +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Optional, + Tuple, + Type, + Union, + cast, +) + +from . import requirements, specifiers, utils, version as version_module + +T = typing.TypeVar("T") +if sys.version_info[:2] >= (3, 8): # pragma: no cover + from typing import Literal, TypedDict +else: # pragma: no cover + if typing.TYPE_CHECKING: + from typing_extensions import Literal, TypedDict + else: + try: + from typing_extensions import Literal, TypedDict + except ImportError: + + class Literal: + def __init_subclass__(*_args, **_kwargs): + pass + + class TypedDict: + def __init_subclass__(*_args, **_kwargs): + pass + + +try: + ExceptionGroup +except NameError: # pragma: no cover + + class ExceptionGroup(Exception): # noqa: N818 + """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11. + + If :external:exc:`ExceptionGroup` is already defined by Python itself, + that version is used instead. + """ + + message: str + exceptions: List[Exception] + + def __init__(self, message: str, exceptions: List[Exception]) -> None: + self.message = message + self.exceptions = exceptions + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})" + +else: # pragma: no cover + ExceptionGroup = ExceptionGroup + + +class InvalidMetadata(ValueError): + """A metadata field contains invalid data.""" + + field: str + """The name of the field that contains invalid data.""" + + def __init__(self, field: str, message: str) -> None: + self.field = field + super().__init__(message) + + +# The RawMetadata class attempts to make as few assumptions about the underlying +# serialization formats as possible. The idea is that as long as a serialization +# formats offer some very basic primitives in *some* way then we can support +# serializing to and from that format. +class RawMetadata(TypedDict, total=False): + """A dictionary of raw core metadata. + + Each field in core metadata maps to a key of this dictionary (when data is + provided). The key is lower-case and underscores are used instead of dashes + compared to the equivalent core metadata field. Any core metadata field that + can be specified multiple times or can hold multiple values in a single + field have a key with a plural name. See :class:`Metadata` whose attributes + match the keys of this dictionary. + + Core metadata fields that can be specified multiple times are stored as a + list or dict depending on which is appropriate for the field. Any fields + which hold multiple values in a single field are stored as a list. + + """ + + # Metadata 1.0 - PEP 241 + metadata_version: str + name: str + version: str + platforms: List[str] + summary: str + description: str + keywords: List[str] + home_page: str + author: str + author_email: str + license: str + + # Metadata 1.1 - PEP 314 + supported_platforms: List[str] + download_url: str + classifiers: List[str] + requires: List[str] + provides: List[str] + obsoletes: List[str] + + # Metadata 1.2 - PEP 345 + maintainer: str + maintainer_email: str + requires_dist: List[str] + provides_dist: List[str] + obsoletes_dist: List[str] + requires_python: str + requires_external: List[str] + project_urls: Dict[str, str] + + # Metadata 2.0 + # PEP 426 attempted to completely revamp the metadata format + # but got stuck without ever being able to build consensus on + # it and ultimately ended up withdrawn. + # + # However, a number of tools had started emitting METADATA with + # `2.0` Metadata-Version, so for historical reasons, this version + # was skipped. + + # Metadata 2.1 - PEP 566 + description_content_type: str + provides_extra: List[str] + + # Metadata 2.2 - PEP 643 + dynamic: List[str] + + # Metadata 2.3 - PEP 685 + # No new fields were added in PEP 685, just some edge case were + # tightened up to provide better interoptability. + + +_STRING_FIELDS = { + "author", + "author_email", + "description", + "description_content_type", + "download_url", + "home_page", + "license", + "maintainer", + "maintainer_email", + "metadata_version", + "name", + "requires_python", + "summary", + "version", +} + +_LIST_FIELDS = { + "classifiers", + "dynamic", + "obsoletes", + "obsoletes_dist", + "platforms", + "provides", + "provides_dist", + "provides_extra", + "requires", + "requires_dist", + "requires_external", + "supported_platforms", +} + +_DICT_FIELDS = { + "project_urls", +} + + +def _parse_keywords(data: str) -> List[str]: + """Split a string of comma-separate keyboards into a list of keywords.""" + return [k.strip() for k in data.split(",")] + + +def _parse_project_urls(data: List[str]) -> Dict[str, str]: + """Parse a list of label/URL string pairings separated by a comma.""" + urls = {} + for pair in data: + # Our logic is slightly tricky here as we want to try and do + # *something* reasonable with malformed data. + # + # The main thing that we have to worry about, is data that does + # not have a ',' at all to split the label from the Value. There + # isn't a singular right answer here, and we will fail validation + # later on (if the caller is validating) so it doesn't *really* + # matter, but since the missing value has to be an empty str + # and our return value is dict[str, str], if we let the key + # be the missing value, then they'd have multiple '' values that + # overwrite each other in a accumulating dict. + # + # The other potentional issue is that it's possible to have the + # same label multiple times in the metadata, with no solid "right" + # answer with what to do in that case. As such, we'll do the only + # thing we can, which is treat the field as unparseable and add it + # to our list of unparsed fields. + parts = [p.strip() for p in pair.split(",", 1)] + parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items + + # TODO: The spec doesn't say anything about if the keys should be + # considered case sensitive or not... logically they should + # be case-preserving and case-insensitive, but doing that + # would open up more cases where we might have duplicate + # entries. + label, url = parts + if label in urls: + # The label already exists in our set of urls, so this field + # is unparseable, and we can just add the whole thing to our + # unparseable data and stop processing it. + raise KeyError("duplicate labels in project urls") + urls[label] = url + + return urls + + +def _get_payload(msg: email.message.Message, source: Union[bytes, str]) -> str: + """Get the body of the message.""" + # If our source is a str, then our caller has managed encodings for us, + # and we don't need to deal with it. + if isinstance(source, str): + payload: str = msg.get_payload() + return payload + # If our source is a bytes, then we're managing the encoding and we need + # to deal with it. + else: + bpayload: bytes = msg.get_payload(decode=True) + try: + return bpayload.decode("utf8", "strict") + except UnicodeDecodeError: + raise ValueError("payload in an invalid encoding") + + +# The various parse_FORMAT functions here are intended to be as lenient as +# possible in their parsing, while still returning a correctly typed +# RawMetadata. +# +# To aid in this, we also generally want to do as little touching of the +# data as possible, except where there are possibly some historic holdovers +# that make valid data awkward to work with. +# +# While this is a lower level, intermediate format than our ``Metadata`` +# class, some light touch ups can make a massive difference in usability. + +# Map METADATA fields to RawMetadata. +_EMAIL_TO_RAW_MAPPING = { + "author": "author", + "author-email": "author_email", + "classifier": "classifiers", + "description": "description", + "description-content-type": "description_content_type", + "download-url": "download_url", + "dynamic": "dynamic", + "home-page": "home_page", + "keywords": "keywords", + "license": "license", + "maintainer": "maintainer", + "maintainer-email": "maintainer_email", + "metadata-version": "metadata_version", + "name": "name", + "obsoletes": "obsoletes", + "obsoletes-dist": "obsoletes_dist", + "platform": "platforms", + "project-url": "project_urls", + "provides": "provides", + "provides-dist": "provides_dist", + "provides-extra": "provides_extra", + "requires": "requires", + "requires-dist": "requires_dist", + "requires-external": "requires_external", + "requires-python": "requires_python", + "summary": "summary", + "supported-platform": "supported_platforms", + "version": "version", +} +_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()} + + +def parse_email(data: Union[bytes, str]) -> Tuple[RawMetadata, Dict[str, List[str]]]: + """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``). + + This function returns a two-item tuple of dicts. The first dict is of + recognized fields from the core metadata specification. Fields that can be + parsed and translated into Python's built-in types are converted + appropriately. All other fields are left as-is. Fields that are allowed to + appear multiple times are stored as lists. + + The second dict contains all other fields from the metadata. This includes + any unrecognized fields. It also includes any fields which are expected to + be parsed into a built-in type but were not formatted appropriately. Finally, + any fields that are expected to appear only once but are repeated are + included in this dict. + + """ + raw: Dict[str, Union[str, List[str], Dict[str, str]]] = {} + unparsed: Dict[str, List[str]] = {} + + if isinstance(data, str): + parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data) + else: + parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data) + + # We have to wrap parsed.keys() in a set, because in the case of multiple + # values for a key (a list), the key will appear multiple times in the + # list of keys, but we're avoiding that by using get_all(). + for name in frozenset(parsed.keys()): + # Header names in RFC are case insensitive, so we'll normalize to all + # lower case to make comparisons easier. + name = name.lower() + + # We use get_all() here, even for fields that aren't multiple use, + # because otherwise someone could have e.g. two Name fields, and we + # would just silently ignore it rather than doing something about it. + headers = parsed.get_all(name) or [] + + # The way the email module works when parsing bytes is that it + # unconditionally decodes the bytes as ascii using the surrogateescape + # handler. When you pull that data back out (such as with get_all() ), + # it looks to see if the str has any surrogate escapes, and if it does + # it wraps it in a Header object instead of returning the string. + # + # As such, we'll look for those Header objects, and fix up the encoding. + value = [] + # Flag if we have run into any issues processing the headers, thus + # signalling that the data belongs in 'unparsed'. + valid_encoding = True + for h in headers: + # It's unclear if this can return more types than just a Header or + # a str, so we'll just assert here to make sure. + assert isinstance(h, (email.header.Header, str)) + + # If it's a header object, we need to do our little dance to get + # the real data out of it. In cases where there is invalid data + # we're going to end up with mojibake, but there's no obvious, good + # way around that without reimplementing parts of the Header object + # ourselves. + # + # That should be fine since, if mojibacked happens, this key is + # going into the unparsed dict anyways. + if isinstance(h, email.header.Header): + # The Header object stores it's data as chunks, and each chunk + # can be independently encoded, so we'll need to check each + # of them. + chunks: List[Tuple[bytes, Optional[str]]] = [] + for bin, encoding in email.header.decode_header(h): + try: + bin.decode("utf8", "strict") + except UnicodeDecodeError: + # Enable mojibake. + encoding = "latin1" + valid_encoding = False + else: + encoding = "utf8" + chunks.append((bin, encoding)) + + # Turn our chunks back into a Header object, then let that + # Header object do the right thing to turn them into a + # string for us. + value.append(str(email.header.make_header(chunks))) + # This is already a string, so just add it. + else: + value.append(h) + + # We've processed all of our values to get them into a list of str, + # but we may have mojibake data, in which case this is an unparsed + # field. + if not valid_encoding: + unparsed[name] = value + continue + + raw_name = _EMAIL_TO_RAW_MAPPING.get(name) + if raw_name is None: + # This is a bit of a weird situation, we've encountered a key that + # we don't know what it means, so we don't know whether it's meant + # to be a list or not. + # + # Since we can't really tell one way or another, we'll just leave it + # as a list, even though it may be a single item list, because that's + # what makes the most sense for email headers. + unparsed[name] = value + continue + + # If this is one of our string fields, then we'll check to see if our + # value is a list of a single item. If it is then we'll assume that + # it was emitted as a single string, and unwrap the str from inside + # the list. + # + # If it's any other kind of data, then we haven't the faintest clue + # what we should parse it as, and we have to just add it to our list + # of unparsed stuff. + if raw_name in _STRING_FIELDS and len(value) == 1: + raw[raw_name] = value[0] + # If this is one of our list of string fields, then we can just assign + # the value, since email *only* has strings, and our get_all() call + # above ensures that this is a list. + elif raw_name in _LIST_FIELDS: + raw[raw_name] = value + # Special Case: Keywords + # The keywords field is implemented in the metadata spec as a str, + # but it conceptually is a list of strings, and is serialized using + # ", ".join(keywords), so we'll do some light data massaging to turn + # this into what it logically is. + elif raw_name == "keywords" and len(value) == 1: + raw[raw_name] = _parse_keywords(value[0]) + # Special Case: Project-URL + # The project urls is implemented in the metadata spec as a list of + # specially-formatted strings that represent a key and a value, which + # is fundamentally a mapping, however the email format doesn't support + # mappings in a sane way, so it was crammed into a list of strings + # instead. + # + # We will do a little light data massaging to turn this into a map as + # it logically should be. + elif raw_name == "project_urls": + try: + raw[raw_name] = _parse_project_urls(value) + except KeyError: + unparsed[name] = value + # Nothing that we've done has managed to parse this, so it'll just + # throw it in our unparseable data and move on. + else: + unparsed[name] = value + + # We need to support getting the Description from the message payload in + # addition to getting it from the the headers. This does mean, though, there + # is the possibility of it being set both ways, in which case we put both + # in 'unparsed' since we don't know which is right. + try: + payload = _get_payload(parsed, data) + except ValueError: + unparsed.setdefault("description", []).append( + parsed.get_payload(decode=isinstance(data, bytes)) + ) + else: + if payload: + # Check to see if we've already got a description, if so then both + # it, and this body move to unparseable. + if "description" in raw: + description_header = cast(str, raw.pop("description")) + unparsed.setdefault("description", []).extend( + [description_header, payload] + ) + elif "description" in unparsed: + unparsed["description"].append(payload) + else: + raw["description"] = payload + + # We need to cast our `raw` to a metadata, because a TypedDict only support + # literal key names, but we're computing our key names on purpose, but the + # way this function is implemented, our `TypedDict` can only have valid key + # names. + return cast(RawMetadata, raw), unparsed + + +_NOT_FOUND = object() + + +# Keep the two values in sync. +_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"] +_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3"] + +_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"]) + + +class _Validator(Generic[T]): + """Validate a metadata field. + + All _process_*() methods correspond to a core metadata field. The method is + called with the field's raw value. If the raw value is valid it is returned + in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field). + If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause + as appropriate). + """ + + name: str + raw_name: str + added: _MetadataVersion + + def __init__( + self, + *, + added: _MetadataVersion = "1.0", + ) -> None: + self.added = added + + def __set_name__(self, _owner: "Metadata", name: str) -> None: + self.name = name + self.raw_name = _RAW_TO_EMAIL_MAPPING[name] + + def __get__(self, instance: "Metadata", _owner: Type["Metadata"]) -> T: + # With Python 3.8, the caching can be replaced with functools.cached_property(). + # No need to check the cache as attribute lookup will resolve into the + # instance's __dict__ before __get__ is called. + cache = instance.__dict__ + value = instance._raw.get(self.name) + + # To make the _process_* methods easier, we'll check if the value is None + # and if this field is NOT a required attribute, and if both of those + # things are true, we'll skip the the converter. This will mean that the + # converters never have to deal with the None union. + if self.name in _REQUIRED_ATTRS or value is not None: + try: + converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}") + except AttributeError: + pass + else: + value = converter(value) + + cache[self.name] = value + try: + del instance._raw[self.name] # type: ignore[misc] + except KeyError: + pass + + return cast(T, value) + + def _invalid_metadata( + self, msg: str, cause: Optional[Exception] = None + ) -> InvalidMetadata: + exc = InvalidMetadata( + self.raw_name, msg.format_map({"field": repr(self.raw_name)}) + ) + exc.__cause__ = cause + return exc + + def _process_metadata_version(self, value: str) -> _MetadataVersion: + # Implicitly makes Metadata-Version required. + if value not in _VALID_METADATA_VERSIONS: + raise self._invalid_metadata(f"{value!r} is not a valid metadata version") + return cast(_MetadataVersion, value) + + def _process_name(self, value: str) -> str: + if not value: + raise self._invalid_metadata("{field} is a required field") + # Validate the name as a side-effect. + try: + utils.canonicalize_name(value, validate=True) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) + else: + return value + + def _process_version(self, value: str) -> version_module.Version: + if not value: + raise self._invalid_metadata("{field} is a required field") + try: + return version_module.parse(value) + except version_module.InvalidVersion as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) + + def _process_summary(self, value: str) -> str: + """Check the field contains no newlines.""" + if "\n" in value: + raise self._invalid_metadata("{field} must be a single line") + return value + + def _process_description_content_type(self, value: str) -> str: + content_types = {"text/plain", "text/x-rst", "text/markdown"} + message = email.message.EmailMessage() + message["content-type"] = value + + content_type, parameters = ( + # Defaults to `text/plain` if parsing failed. + message.get_content_type().lower(), + message["content-type"].params, + ) + # Check if content-type is valid or defaulted to `text/plain` and thus was + # not parseable. + if content_type not in content_types or content_type not in value.lower(): + raise self._invalid_metadata( + f"{{field}} must be one of {list(content_types)}, not {value!r}" + ) + + charset = parameters.get("charset", "UTF-8") + if charset != "UTF-8": + raise self._invalid_metadata( + f"{{field}} can only specify the UTF-8 charset, not {list(charset)}" + ) + + markdown_variants = {"GFM", "CommonMark"} + variant = parameters.get("variant", "GFM") # Use an acceptable default. + if content_type == "text/markdown" and variant not in markdown_variants: + raise self._invalid_metadata( + f"valid Markdown variants for {{field}} are {list(markdown_variants)}, " + f"not {variant!r}", + ) + return value + + def _process_dynamic(self, value: List[str]) -> List[str]: + for dynamic_field in map(str.lower, value): + if dynamic_field in {"name", "version", "metadata-version"}: + raise self._invalid_metadata( + f"{value!r} is not allowed as a dynamic field" + ) + elif dynamic_field not in _EMAIL_TO_RAW_MAPPING: + raise self._invalid_metadata(f"{value!r} is not a valid dynamic field") + return list(map(str.lower, value)) + + def _process_provides_extra( + self, + value: List[str], + ) -> List[utils.NormalizedName]: + normalized_names = [] + try: + for name in value: + normalized_names.append(utils.canonicalize_name(name, validate=True)) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{name!r} is invalid for {{field}}", cause=exc + ) + else: + return normalized_names + + def _process_requires_python(self, value: str) -> specifiers.SpecifierSet: + try: + return specifiers.SpecifierSet(value) + except specifiers.InvalidSpecifier as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) + + def _process_requires_dist( + self, + value: List[str], + ) -> List[requirements.Requirement]: + reqs = [] + try: + for req in value: + reqs.append(requirements.Requirement(req)) + except requirements.InvalidRequirement as exc: + raise self._invalid_metadata(f"{req!r} is invalid for {{field}}", cause=exc) + else: + return reqs + + +class Metadata: + """Representation of distribution metadata. + + Compared to :class:`RawMetadata`, this class provides objects representing + metadata fields instead of only using built-in types. Any invalid metadata + will cause :exc:`InvalidMetadata` to be raised (with a + :py:attr:`~BaseException.__cause__` attribute as appropriate). + """ + + _raw: RawMetadata + + @classmethod + def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> "Metadata": + """Create an instance from :class:`RawMetadata`. + + If *validate* is true, all metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + ins = cls() + ins._raw = data.copy() # Mutations occur due to caching enriched values. + + if validate: + exceptions: List[Exception] = [] + try: + metadata_version = ins.metadata_version + metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version) + except InvalidMetadata as metadata_version_exc: + exceptions.append(metadata_version_exc) + metadata_version = None + + # Make sure to check for the fields that are present, the required + # fields (so their absence can be reported). + fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS + # Remove fields that have already been checked. + fields_to_check -= {"metadata_version"} + + for key in fields_to_check: + try: + if metadata_version: + # Can't use getattr() as that triggers descriptor protocol which + # will fail due to no value for the instance argument. + try: + field_metadata_version = cls.__dict__[key].added + except KeyError: + exc = InvalidMetadata(key, f"unrecognized field: {key!r}") + exceptions.append(exc) + continue + field_age = _VALID_METADATA_VERSIONS.index( + field_metadata_version + ) + if field_age > metadata_age: + field = _RAW_TO_EMAIL_MAPPING[key] + exc = InvalidMetadata( + field, + "{field} introduced in metadata version " + "{field_metadata_version}, not {metadata_version}", + ) + exceptions.append(exc) + continue + getattr(ins, key) + except InvalidMetadata as exc: + exceptions.append(exc) + + if exceptions: + raise ExceptionGroup("invalid metadata", exceptions) + + return ins + + @classmethod + def from_email( + cls, data: Union[bytes, str], *, validate: bool = True + ) -> "Metadata": + """Parse metadata from email headers. + + If *validate* is true, the metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + raw, unparsed = parse_email(data) + + if validate: + exceptions: list[Exception] = [] + for unparsed_key in unparsed: + if unparsed_key in _EMAIL_TO_RAW_MAPPING: + message = f"{unparsed_key!r} has invalid data" + else: + message = f"unrecognized field: {unparsed_key!r}" + exceptions.append(InvalidMetadata(unparsed_key, message)) + + if exceptions: + raise ExceptionGroup("unparsed", exceptions) + + try: + return cls.from_raw(raw, validate=validate) + except ExceptionGroup as exc_group: + raise ExceptionGroup( + "invalid or unparsed metadata", exc_group.exceptions + ) from None + + metadata_version: _Validator[_MetadataVersion] = _Validator() + """:external:ref:`core-metadata-metadata-version` + (required; validated to be a valid metadata version)""" + name: _Validator[str] = _Validator() + """:external:ref:`core-metadata-name` + (required; validated using :func:`~packaging.utils.canonicalize_name` and its + *validate* parameter)""" + version: _Validator[version_module.Version] = _Validator() + """:external:ref:`core-metadata-version` (required)""" + dynamic: _Validator[Optional[List[str]]] = _Validator( + added="2.2", + ) + """:external:ref:`core-metadata-dynamic` + (validated against core metadata field names and lowercased)""" + platforms: _Validator[Optional[List[str]]] = _Validator() + """:external:ref:`core-metadata-platform`""" + supported_platforms: _Validator[Optional[List[str]]] = _Validator(added="1.1") + """:external:ref:`core-metadata-supported-platform`""" + summary: _Validator[Optional[str]] = _Validator() + """:external:ref:`core-metadata-summary` (validated to contain no newlines)""" + description: _Validator[Optional[str]] = _Validator() # TODO 2.1: can be in body + """:external:ref:`core-metadata-description`""" + description_content_type: _Validator[Optional[str]] = _Validator(added="2.1") + """:external:ref:`core-metadata-description-content-type` (validated)""" + keywords: _Validator[Optional[List[str]]] = _Validator() + """:external:ref:`core-metadata-keywords`""" + home_page: _Validator[Optional[str]] = _Validator() + """:external:ref:`core-metadata-home-page`""" + download_url: _Validator[Optional[str]] = _Validator(added="1.1") + """:external:ref:`core-metadata-download-url`""" + author: _Validator[Optional[str]] = _Validator() + """:external:ref:`core-metadata-author`""" + author_email: _Validator[Optional[str]] = _Validator() + """:external:ref:`core-metadata-author-email`""" + maintainer: _Validator[Optional[str]] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer`""" + maintainer_email: _Validator[Optional[str]] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer-email`""" + license: _Validator[Optional[str]] = _Validator() + """:external:ref:`core-metadata-license`""" + classifiers: _Validator[Optional[List[str]]] = _Validator(added="1.1") + """:external:ref:`core-metadata-classifier`""" + requires_dist: _Validator[Optional[List[requirements.Requirement]]] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-dist`""" + requires_python: _Validator[Optional[specifiers.SpecifierSet]] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-python`""" + # Because `Requires-External` allows for non-PEP 440 version specifiers, we + # don't do any processing on the values. + requires_external: _Validator[Optional[List[str]]] = _Validator(added="1.2") + """:external:ref:`core-metadata-requires-external`""" + project_urls: _Validator[Optional[Dict[str, str]]] = _Validator(added="1.2") + """:external:ref:`core-metadata-project-url`""" + # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation + # regardless of metadata version. + provides_extra: _Validator[Optional[List[utils.NormalizedName]]] = _Validator( + added="2.1", + ) + """:external:ref:`core-metadata-provides-extra`""" + provides_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2") + """:external:ref:`core-metadata-provides-dist`""" + obsoletes_dist: _Validator[Optional[List[str]]] = _Validator(added="1.2") + """:external:ref:`core-metadata-obsoletes-dist`""" + requires: _Validator[Optional[List[str]]] = _Validator(added="1.1") + """``Requires`` (deprecated)""" + provides: _Validator[Optional[List[str]]] = _Validator(added="1.1") + """``Provides`` (deprecated)""" + obsoletes: _Validator[Optional[List[str]]] = _Validator(added="1.1") + """``Obsoletes`` (deprecated)""" diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/py.typed b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/requirements.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/requirements.py new file mode 100644 index 0000000000000000000000000000000000000000..bdc43a7e98d87dba0c2069bfb4554f71d228cad4 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/requirements.py @@ -0,0 +1,90 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from typing import Any, Iterator, Optional, Set + +from ._parser import parse_requirement as _parse_requirement +from ._tokenizer import ParserSyntaxError +from .markers import Marker, _normalize_extra_values +from .specifiers import SpecifierSet +from .utils import canonicalize_name + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +class Requirement: + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string: str) -> None: + try: + parsed = _parse_requirement(requirement_string) + except ParserSyntaxError as e: + raise InvalidRequirement(str(e)) from e + + self.name: str = parsed.name + self.url: Optional[str] = parsed.url or None + self.extras: Set[str] = set(parsed.extras or []) + self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) + self.marker: Optional[Marker] = None + if parsed.marker is not None: + self.marker = Marker.__new__(Marker) + self.marker._markers = _normalize_extra_values(parsed.marker) + + def _iter_parts(self, name: str) -> Iterator[str]: + yield name + + if self.extras: + formatted_extras = ",".join(sorted(self.extras)) + yield f"[{formatted_extras}]" + + if self.specifier: + yield str(self.specifier) + + if self.url: + yield f"@ {self.url}" + if self.marker: + yield " " + + if self.marker: + yield f"; {self.marker}" + + def __str__(self) -> str: + return "".join(self._iter_parts(self.name)) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash( + ( + self.__class__.__name__, + *self._iter_parts(canonicalize_name(self.name)), + ) + ) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Requirement): + return NotImplemented + + return ( + canonicalize_name(self.name) == canonicalize_name(other.name) + and self.extras == other.extras + and self.specifier == other.specifier + and self.url == other.url + and self.marker == other.marker + ) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/specifiers.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/specifiers.py new file mode 100644 index 0000000000000000000000000000000000000000..2d015bab5958fd9767cf5c9e449f2fa33292c962 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/specifiers.py @@ -0,0 +1,1017 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier + from packaging.version import Version +""" + +import abc +import itertools +import re +from typing import Callable, Iterable, Iterator, List, Optional, Tuple, TypeVar, Union + +from .utils import canonicalize_version +from .version import Version + +UnparsedVersion = Union[Version, str] +UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) +CallableOperator = Callable[[Version, str], bool] + + +def _coerce_version(version: UnparsedVersion) -> Version: + if not isinstance(version, Version): + version = Version(version) + return version + + +class InvalidSpecifier(ValueError): + """ + Raised when attempting to create a :class:`Specifier` with a specifier + string that is invalid. + + >>> Specifier("lolwat") + Traceback (most recent call last): + ... + packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' + """ + + +class BaseSpecifier(metaclass=abc.ABCMeta): + @abc.abstractmethod + def __str__(self) -> str: + """ + Returns the str representation of this Specifier-like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self) -> int: + """ + Returns a hash value for this Specifier-like object. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier-like + objects are equal. + + :param other: The other object to check against. + """ + + @property + @abc.abstractmethod + def prereleases(self) -> Optional[bool]: + """Whether or not pre-releases as a whole are allowed. + + This can be set to either ``True`` or ``False`` to explicitly enable or disable + prereleases or it can be set to ``None`` (the default) to use default semantics. + """ + + @prereleases.setter + def prereleases(self, value: bool) -> None: + """Setter for :attr:`prereleases`. + + :param value: The value to set. + """ + + @abc.abstractmethod + def contains(self, item: str, prereleases: Optional[bool] = None) -> bool: + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None + ) -> Iterator[UnparsedVersionVar]: + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class Specifier(BaseSpecifier): + """This class abstracts handling of version specifiers. + + .. tip:: + + It is generally not required to instantiate this manually. You should instead + prefer to work with :class:`SpecifierSet` instead, which can parse + comma-separated version specifiers (which is what package metadata contains). + """ + + _operator_regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + """ + _version_regex_str = r""" + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s;)]* # The arbitrary version can be just about anything, + # we match everything except for whitespace, a + # semi-colon for marker support, and a closing paren + # since versions can be enclosed in them. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + + # You cannot use a wild card and a pre-release, post-release, a dev or + # local version together so group them with a | and make them optional. + (?: + \.\* # Wild card syntax of .* + | + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + def __init__(self, spec: str = "", prereleases: Optional[bool] = None) -> None: + """Initialize a Specifier instance. + + :param spec: + The string representation of a specifier which will be parsed and + normalized before use. + :param prereleases: + This tells the specifier if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + :raises InvalidSpecifier: + If the given specifier is invalid (i.e. bad syntax). + """ + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier(f"Invalid specifier: '{spec}'") + + self._spec: Tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515 + @property # type: ignore[override] + def prereleases(self) -> bool: + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "==="]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if Version(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + @property + def operator(self) -> str: + """The operator of this specifier. + + >>> Specifier("==1.2.3").operator + '==' + """ + return self._spec[0] + + @property + def version(self) -> str: + """The version of this specifier. + + >>> Specifier("==1.2.3").version + '1.2.3' + """ + return self._spec[1] + + def __repr__(self) -> str: + """A representation of the Specifier that shows all internal state. + + >>> Specifier('>=1.0.0') + =1.0.0')> + >>> Specifier('>=1.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> Specifier('>=1.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"<{self.__class__.__name__}({str(self)!r}{pre})>" + + def __str__(self) -> str: + """A string representation of the Specifier that can be round-tripped. + + >>> str(Specifier('>=1.0.0')) + '>=1.0.0' + >>> str(Specifier('>=1.0.0', prereleases=False)) + '>=1.0.0' + """ + return "{}{}".format(*self._spec) + + @property + def _canonical_spec(self) -> Tuple[str, str]: + canonical_version = canonicalize_version( + self._spec[1], + strip_trailing_zero=(self._spec[0] != "~="), + ) + return self._spec[0], canonical_version + + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + """Whether or not the two Specifier-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") + True + >>> (Specifier("==1.2.3", prereleases=False) == + ... Specifier("==1.2.3", prereleases=True)) + True + >>> Specifier("==1.2.3") == "==1.2.3" + True + >>> Specifier("==1.2.3") == Specifier("==1.2.4") + False + >>> Specifier("==1.2.3") == Specifier("~=1.2.3") + False + """ + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable + + def _compare_compatible(self, prospective: Version, spec: str) -> bool: + + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore suffix segments. + prefix = _version_join( + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + def _compare_equal(self, prospective: Version, spec: str) -> bool: + + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + normalized_prospective = canonicalize_version( + prospective.public, strip_trailing_zero=False + ) + # Get the normalized version string ignoring the trailing .* + normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False) + # Split the spec out by bangs and dots, and pretend that there is + # an implicit dot in between a release segment and a pre-release segment. + split_spec = _version_split(normalized_spec) + + # Split the prospective version out by bangs and dots, and pretend + # that there is an implicit dot in between a release segment and + # a pre-release segment. + split_prospective = _version_split(normalized_prospective) + + # 0-pad the prospective version before shortening it to get the correct + # shortened version. + padded_prospective, _ = _pad_version(split_prospective, split_spec) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = padded_prospective[: len(split_spec)] + + return shortened_prospective == split_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + def _compare_not_equal(self, prospective: Version, spec: str) -> bool: + return not self._compare_equal(prospective, spec) + + def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: + + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: + + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: + return str(prospective).lower() == str(spec).lower() + + def __contains__(self, item: Union[str, Version]) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in Specifier(">=1.2.3") + True + >>> Version("1.2.3") in Specifier(">=1.2.3") + True + >>> "1.0.0" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) + True + """ + return self.contains(item) + + def contains( + self, item: UnparsedVersion, prereleases: Optional[bool] = None + ) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this Specifier. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> Specifier(">=1.2.3").contains("1.2.3") + True + >>> Specifier(">=1.2.3").contains(Version("1.2.3")) + True + >>> Specifier(">=1.2.3").contains("1.0.0") + False + >>> Specifier(">=1.2.3").contains("1.3.0a1") + False + >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1") + True + >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True) + True + """ + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version, this allows us to have a shortcut for + # "2.0" in Specifier(">=2") + normalized_item = _coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifier. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(Specifier().contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) + ['1.2.3', '1.3', ] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) + ['1.5a1'] + >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + """ + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = _coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later in case nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version: str) -> List[str]: + """Split version into components. + + The split components are intended for version comparison. The logic does + not attempt to retain the original version string, so joining the + components back with :func:`_version_join` may not produce the original + version string. + """ + result: List[str] = [] + + epoch, _, rest = version.rpartition("!") + result.append(epoch or "0") + + for item in rest.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _version_join(components: List[str]) -> str: + """Join split version components into a version string. + + This function assumes the input came from :func:`_version_split`, where the + first component must be the epoch (either empty or numeric), and all other + components numeric. + """ + epoch, *rest = components + return f"{epoch}!{'.'.join(rest)}" + + +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: List[str], right: List[str]) -> Tuple[List[str], List[str]]: + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return ( + list(itertools.chain.from_iterable(left_split)), + list(itertools.chain.from_iterable(right_split)), + ) + + +class SpecifierSet(BaseSpecifier): + """This class abstracts handling of a set of version specifiers. + + It can be passed a single specifier (``>=3.0``), a comma-separated list of + specifiers (``>=3.0,!=3.1``), or no specifier at all. + """ + + def __init__( + self, specifiers: str = "", prereleases: Optional[bool] = None + ) -> None: + """Initialize a SpecifierSet instance. + + :param specifiers: + The string representation of a specifier or a comma-separated list of + specifiers which will be parsed and normalized before use. + :param prereleases: + This tells the SpecifierSet if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + + :raises InvalidSpecifier: + If the given ``specifiers`` are not parseable than this exception will be + raised. + """ + + # Split on `,` to break each individual specifier into it's own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Make each individual specifier a Specifier and save in a frozen set for later. + self._specs = frozenset(map(Specifier, split_specifiers)) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + @property + def prereleases(self) -> Optional[bool]: + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __repr__(self) -> str: + """A representation of the specifier set that shows all internal state. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> SpecifierSet('>=1.0.0,!=2.0.0') + =1.0.0')> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"" + + def __str__(self) -> str: + """A string representation of the specifier set that can be round-tripped. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) + '!=1.0.1,>=1.0.0' + >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) + '!=1.0.1,>=1.0.0' + """ + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self) -> int: + return hash(self._specs) + + def __and__(self, other: Union["SpecifierSet", str]) -> "SpecifierSet": + """Return a SpecifierSet which is a combination of the two sets. + + :param other: The other object to combine with. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' + =1.0.0')> + >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') + =1.0.0')> + """ + if isinstance(other, str): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease " + "overrides." + ) + + return specifier + + def __eq__(self, other: object) -> bool: + """Whether or not the two SpecifierSet-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == + ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") + False + """ + if isinstance(other, (str, Specifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __len__(self) -> int: + """Returns the number of specifiers in this specifier set.""" + return len(self._specs) + + def __iter__(self) -> Iterator[Specifier]: + """ + Returns an iterator over all the underlying :class:`Specifier` instances + in this specifier set. + + >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) + [, =1.0.0')>] + """ + return iter(self._specs) + + def __contains__(self, item: UnparsedVersion) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) + True + """ + return self.contains(item) + + def contains( + self, + item: UnparsedVersion, + prereleases: Optional[bool] = None, + installed: Optional[bool] = None, + ) -> bool: + """Return whether or not the item is contained in this SpecifierSet. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this SpecifierSet. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) + True + """ + # Ensure that our item is a Version instance. + if not isinstance(item, Version): + item = Version(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + if installed and item.is_prerelease: + item = Version(item.base_version) + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: Optional[bool] = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifiers in this set. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(SpecifierSet(...).contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) + ['1.3', ] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) + [] + >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + + An "empty" SpecifierSet will filter items based on the presence of prerelease + versions in the set. + + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet("").filter(["1.5a1"])) + ['1.5a1'] + >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + """ + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iter(iterable) + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases. + else: + filtered: List[UnparsedVersionVar] = [] + found_prereleases: List[UnparsedVersionVar] = [] + + for item in iterable: + parsed_version = _coerce_version(item) + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return iter(found_prereleases) + + return iter(filtered) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/tags.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..89f1926137dd2d2a6bd63616bf5b9f722fc8d584 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/tags.py @@ -0,0 +1,571 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import logging +import platform +import re +import struct +import subprocess +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Dict, + FrozenSet, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +MacVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: Dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = struct.calcsize("P") == 4 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_interpreter", "_abi", "_platform", "_hash"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return f"<{self} @ {id(self)}>" + + +def parse_tag(tag: str) -> FrozenSet[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> Union[int, str, None]: + value: Union[int, str, None] = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_").replace(" ", "_") + + +def _is_threaded_cpython(abis: List[str]) -> bool: + """ + Determine if the ABI corresponds to a threaded (`--disable-gil`) build. + + The threaded builds are indicated by a "t" in the abiflags. + """ + if len(abis) == 0: + return False + # expect e.g., cp313 + m = re.match(r"cp\d+(.*)", abis[0]) + if not m: + return False + abiflags = m.group(1) + return "t" in abiflags + + +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`) + builds do not support abi3. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> List[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + threading = debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn): + threading = "t" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}{threading}") + abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}") + return abis + + +def cpython_tags( + python_version: Optional[PythonVersion] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = f"cp{_version_nodot(python_version[:2])}" + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + threading = _is_threaded_cpython(abis) + use_abi3 = _abi3_applies(python_version, threading) + if use_abi3: + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if use_abi3: + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + interpreter = "cp{version}".format( + version=_version_nodot((python_version[0], minor_version)) + ) + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> List[str]: + """ + Return the ABI tag based on EXT_SUFFIX. + """ + # The following are examples of `EXT_SUFFIX`. + # We want to keep the parts which are related to the ABI and remove the + # parts which are related to the platform: + # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310 + # - mac: '.cpython-310-darwin.so' => cp310 + # - win: '.cp310-win_amd64.pyd' => cp310 + # - win: '.pyd' => cp37 (uses _cpython_abis()) + # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73 + # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib' + # => graalpy_38_native + + ext_suffix = _get_config_var("EXT_SUFFIX", warn=True) + if not isinstance(ext_suffix, str) or ext_suffix[0] != ".": + raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')") + parts = ext_suffix.split(".") + if len(parts) < 3: + # CPython3.7 and earlier uses ".pyd" on Windows. + return _cpython_abis(sys.version_info[:2]) + soabi = parts[1] + if soabi.startswith("cpython"): + # non-windows + abi = "cp" + soabi.split("-")[1] + elif soabi.startswith("cp"): + # windows + abi = soabi.split("-")[0] + elif soabi.startswith("pypy"): + abi = "-".join(soabi.split("-")[:2]) + elif soabi.startswith("graalpy"): + abi = "-".join(soabi.split("-")[:3]) + elif soabi: + # pyston, ironpython, others? + abi = soabi + else: + return [] + return [_normalize_string(abi)] + + +def generic_tags( + interpreter: Optional[str] = None, + abis: Optional[Iterable[str]] = None, + platforms: Optional[Iterable[str]] = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + else: + abis = list(abis) + platforms = list(platforms or platform_tags()) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield f"py{_version_nodot(py_version[:2])}" + yield f"py{py_version[0]}" + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield f"py{_version_nodot((py_version[0], minor))}" + + +def compatible_tags( + python_version: Optional[PythonVersion] = None, + interpreter: Optional[str] = None, + platforms: Optional[Iterable[str]] = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: MacVersion, cpu_arch: str) -> List[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: Optional[MacVersion] = None, arch: Optional[str] = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + if version == (10, 16): + # When built against an older macOS SDK, Python will report macOS 10.16 + # instead of the real version. + version_str = subprocess.run( + [ + sys.executable, + "-sS", + "-c", + "import platform; print(platform.mac_ver()[0])", + ], + check=True, + env={"SYSTEM_VERSION_COMPAT": "0"}, + stdout=subprocess.PIPE, + text=True, + ).stdout + version = cast("MacVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + for minor_version in range(version[1], -1, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=10, minor=minor_version, binary_format=binary_format + ) + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + for major_version in range(version[0], 10, -1): + compat_version = major_version, 0 + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=major_version, minor=0, binary_format=binary_format + ) + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + else: + for minor_version in range(16, 3, -1): + compat_version = 10, minor_version + binary_format = "universal2" + yield "macosx_{major}_{minor}_{binary_format}".format( + major=compat_version[0], + minor=compat_version[1], + binary_format=binary_format, + ) + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if not linux.startswith("linux_"): + # we should never be here, just yield the sysconfig one and return + yield linux + return + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv8l" + _, arch = linux.split("_", 1) + archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) + yield from _manylinux.platform_tags(archs) + yield from _musllinux.platform_tags(archs) + for arch in archs: + yield f"linux_{arch}" + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + + Some implementations have a reserved, two-letter abbreviation which will + be returned when appropriate. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + if interp_name == "pp": + interp = "pp3" + elif interp_name == "cp": + interp = "cp" + interpreter_version(warn=warn) + else: + interp = None + yield from compatible_tags(interpreter=interp) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/utils.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c2c2f75aa806282d322c76c2117c0f0fdfb09d25 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/utils.py @@ -0,0 +1,172 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import re +from typing import FrozenSet, NewType, Tuple, Union, cast + +from .tags import Tag, parse_tag +from .version import InvalidVersion, Version + +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidName(ValueError): + """ + An invalid distribution name; users should refer to the packaging user guide. + """ + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + + +# Core metadata spec for `Name` +_validate_regex = re.compile( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE +) +_canonicalize_regex = re.compile(r"[-_.]+") +_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") + + +def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName: + if validate and not _validate_regex.match(name): + raise InvalidName(f"name is invalid: {name!r}") + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) + + +def is_normalized_name(name: str) -> bool: + return _normalized_regex.match(name) is not None + + +def canonicalize_version( + version: Union[Version, str], *, strip_trailing_zero: bool = True +) -> str: + """ + This is very similar to Version.__str__, but has one subtle difference + with the way it handles the release segment. + """ + if isinstance(version, str): + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + else: + parsed = version + + parts = [] + + # Epoch + if parsed.epoch != 0: + parts.append(f"{parsed.epoch}!") + + # Release segment + release_segment = ".".join(str(x) for x in parsed.release) + if strip_trailing_zero: + # NB: This strips trailing '.0's to normalize + release_segment = re.sub(r"(\.0)+$", "", release_segment) + parts.append(release_segment) + + # Pre-release + if parsed.pre is not None: + parts.append("".join(str(x) for x in parsed.pre)) + + # Post-release + if parsed.post is not None: + parts.append(f".post{parsed.post}") + + # Development release + if parsed.dev is not None: + parts.append(f".dev{parsed.dev}") + + # Local version segment + if parsed.local is not None: + parts.append(f"+{parsed.local}") + + return "".join(parts) + + +def parse_wheel_filename( + filename: str, +) -> Tuple[NormalizedName, Version, BuildTag, FrozenSet[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name. + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename}") + name = canonicalize_name(name_part) + + try: + version = Version(parts[1]) + except InvalidVersion as e: + raise InvalidWheelFilename( + f"Invalid wheel filename (invalid version): {filename}" + ) from e + + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in '{filename}'" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> Tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename}") + + name = canonicalize_name(name_part) + + try: + version = Version(version_part) + except InvalidVersion as e: + raise InvalidSdistFilename( + f"Invalid sdist filename (invalid version): {filename}" + ) from e + + return (name, version) diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/version.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/version.py new file mode 100644 index 0000000000000000000000000000000000000000..5faab9bd0dcf28847960162b2b4f13a8a556ef20 --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/packaging/version.py @@ -0,0 +1,563 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from packaging.version import parse, Version +""" + +import itertools +import re +from typing import Any, Callable, NamedTuple, Optional, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["VERSION_PATTERN", "parse", "Version", "InvalidVersion"] + +LocalType = Tuple[Union[int, str], ...] + +CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]] +CmpLocalType = Union[ + NegativeInfinityType, + Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...], +] +CmpKey = Tuple[ + int, + Tuple[int, ...], + CmpPrePostDevType, + CmpPrePostDevType, + CmpPrePostDevType, + CmpLocalType, +] +VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] + + +class _Version(NamedTuple): + epoch: int + release: Tuple[int, ...] + dev: Optional[Tuple[str, int]] + pre: Optional[Tuple[str, int]] + post: Optional[Tuple[str, int]] + local: Optional[LocalType] + + +def parse(version: str) -> "Version": + """Parse the given version string. + + >>> parse('1.0.dev1') + + + :param version: The version string to parse. + :raises InvalidVersion: When the version string is not a valid version. + """ + return Version(version) + + +class InvalidVersion(ValueError): + """Raised when a version string is not a valid version. + + >>> Version("invalid") + Traceback (most recent call last): + ... + packaging.version.InvalidVersion: Invalid version: 'invalid' + """ + + +class _BaseVersion: + _key: Tuple[Any, ...] + + def __hash__(self) -> int: + return hash(self._key) + + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key < other._key + + def __le__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: "_BaseVersion") -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +_VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?Palpha|a|beta|b|preview|pre|c|rc)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
+
+class Version(_BaseVersion):
+    """This class abstracts handling of a project's versions.
+
+    A :class:`Version` instance is comparison aware and can be compared and
+    sorted using the standard Python interfaces.
+
+    >>> v1 = Version("1.0a5")
+    >>> v2 = Version("1.0")
+    >>> v1
+    
+    >>> v2
+    
+    >>> v1 < v2
+    True
+    >>> v1 == v2
+    False
+    >>> v1 > v2
+    False
+    >>> v1 >= v2
+    False
+    >>> v1 <= v2
+    True
+    """
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+    _key: CmpKey
+
+    def __init__(self, version: str) -> None:
+        """Initialize a Version object.
+
+        :param version:
+            The string representation of a version which will be parsed and normalized
+            before use.
+        :raises InvalidVersion:
+            If the ``version`` does not conform to PEP 440 in any way then this
+            exception will be raised.
+        """
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion(f"Invalid version: '{version}'")
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self) -> str:
+        """A representation of the Version that shows all internal state.
+
+        >>> Version('1.0.0')
+        
+        """
+        return f""
+
+    def __str__(self) -> str:
+        """A string representation of the version that can be rounded-tripped.
+
+        >>> str(Version("1.0a5"))
+        '1.0a5'
+        """
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(f".post{self.post}")
+
+        # Development release
+        if self.dev is not None:
+            parts.append(f".dev{self.dev}")
+
+        # Local version segment
+        if self.local is not None:
+            parts.append(f"+{self.local}")
+
+        return "".join(parts)
+
+    @property
+    def epoch(self) -> int:
+        """The epoch of the version.
+
+        >>> Version("2.0.0").epoch
+        0
+        >>> Version("1!2.0.0").epoch
+        1
+        """
+        return self._version.epoch
+
+    @property
+    def release(self) -> Tuple[int, ...]:
+        """The components of the "release" segment of the version.
+
+        >>> Version("1.2.3").release
+        (1, 2, 3)
+        >>> Version("2.0.0").release
+        (2, 0, 0)
+        >>> Version("1!2.0.0.post0").release
+        (2, 0, 0)
+
+        Includes trailing zeroes but not the epoch or any pre-release / development /
+        post-release suffixes.
+        """
+        return self._version.release
+
+    @property
+    def pre(self) -> Optional[Tuple[str, int]]:
+        """The pre-release segment of the version.
+
+        >>> print(Version("1.2.3").pre)
+        None
+        >>> Version("1.2.3a1").pre
+        ('a', 1)
+        >>> Version("1.2.3b1").pre
+        ('b', 1)
+        >>> Version("1.2.3rc1").pre
+        ('rc', 1)
+        """
+        return self._version.pre
+
+    @property
+    def post(self) -> Optional[int]:
+        """The post-release number of the version.
+
+        >>> print(Version("1.2.3").post)
+        None
+        >>> Version("1.2.3.post1").post
+        1
+        """
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self) -> Optional[int]:
+        """The development number of the version.
+
+        >>> print(Version("1.2.3").dev)
+        None
+        >>> Version("1.2.3.dev1").dev
+        1
+        """
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self) -> Optional[str]:
+        """The local version segment of the version.
+
+        >>> print(Version("1.2.3").local)
+        None
+        >>> Version("1.2.3+abc").local
+        'abc'
+        """
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self) -> str:
+        """The public portion of the version.
+
+        >>> Version("1.2.3").public
+        '1.2.3'
+        >>> Version("1.2.3+abc").public
+        '1.2.3'
+        >>> Version("1.2.3+abc.dev1").public
+        '1.2.3'
+        """
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self) -> str:
+        """The "base version" of the version.
+
+        >>> Version("1.2.3").base_version
+        '1.2.3'
+        >>> Version("1.2.3+abc").base_version
+        '1.2.3'
+        >>> Version("1!1.2.3+abc.dev1").base_version
+        '1!1.2.3'
+
+        The "base version" is the public version of the project without any pre or post
+        release markers.
+        """
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self) -> bool:
+        """Whether this version is a pre-release.
+
+        >>> Version("1.2.3").is_prerelease
+        False
+        >>> Version("1.2.3a1").is_prerelease
+        True
+        >>> Version("1.2.3b1").is_prerelease
+        True
+        >>> Version("1.2.3rc1").is_prerelease
+        True
+        >>> Version("1.2.3dev1").is_prerelease
+        True
+        """
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self) -> bool:
+        """Whether this version is a post-release.
+
+        >>> Version("1.2.3").is_postrelease
+        False
+        >>> Version("1.2.3.post1").is_postrelease
+        True
+        """
+        return self.post is not None
+
+    @property
+    def is_devrelease(self) -> bool:
+        """Whether this version is a development release.
+
+        >>> Version("1.2.3").is_devrelease
+        False
+        >>> Version("1.2.3.dev1").is_devrelease
+        True
+        """
+        return self.dev is not None
+
+    @property
+    def major(self) -> int:
+        """The first item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").major
+        1
+        """
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self) -> int:
+        """The second item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").minor
+        2
+        >>> Version("1").minor
+        0
+        """
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self) -> int:
+        """The third item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").micro
+        3
+        >>> Version("1").micro
+        0
+        """
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+def _parse_letter_version(
+    letter: Optional[str], number: Union[str, bytes, SupportsInt, None]
+) -> Optional[Tuple[str, int]]:
+
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+    if not letter and number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: Optional[str]) -> Optional[LocalType]:
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch: int,
+    release: Tuple[int, ...],
+    pre: Optional[Tuple[str, int]],
+    post: Optional[Tuple[str, int]],
+    dev: Optional[Tuple[str, int]],
+    local: Optional[LocalType],
+) -> CmpKey:
+
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre: CmpPrePostDevType = NegativeInfinity
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post: CmpPrePostDevType = NegativeInfinity
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev: CmpPrePostDevType = Infinity
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local: CmpLocalType = NegativeInfinity
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..aef2821b83f6ac1730d063d8ce939134cc2105a7
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__init__.py
@@ -0,0 +1,342 @@
+"""
+Utilities for determining application-specific dirs. See  for details and
+usage.
+"""
+from __future__ import annotations
+
+import os
+import sys
+from pathlib import Path
+
+if sys.version_info >= (3, 8):  # pragma: no cover (py38+)
+    from typing import Literal
+else:  # pragma: no cover (py38+)
+    from ..typing_extensions import Literal
+
+from .api import PlatformDirsABC
+from .version import __version__
+from .version import __version_tuple__ as __version_info__
+
+
+def _set_platform_dir_class() -> type[PlatformDirsABC]:
+    if sys.platform == "win32":
+        from .windows import Windows as Result
+    elif sys.platform == "darwin":
+        from .macos import MacOS as Result
+    else:
+        from .unix import Unix as Result
+
+    if os.getenv("ANDROID_DATA") == "/data" and os.getenv("ANDROID_ROOT") == "/system":
+
+        if os.getenv("SHELL") or os.getenv("PREFIX"):
+            return Result
+
+        from .android import _android_folder
+
+        if _android_folder() is not None:
+            from .android import Android
+
+            return Android  # return to avoid redefinition of result
+
+    return Result
+
+
+PlatformDirs = _set_platform_dir_class()  #: Currently active platform
+AppDirs = PlatformDirs  #: Backwards compatibility with appdirs
+
+
+def user_data_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: data directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_dir
+
+
+def site_data_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    multipath: bool = False,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param multipath: See `roaming `.
+    :returns: data directory shared by users
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_dir
+
+
+def user_config_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: config directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_dir
+
+
+def site_config_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    multipath: bool = False,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param multipath: See `roaming `.
+    :returns: config directory shared by the users
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_dir
+
+
+def user_cache_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `roaming `.
+    :returns: cache directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_dir
+
+
+def user_state_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: state directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_dir
+
+
+def user_log_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `roaming `.
+    :returns: log directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_dir
+
+
+def user_documents_dir() -> str:
+    """
+    :returns: documents directory tied to the user
+    """
+    return PlatformDirs().user_documents_dir
+
+
+def user_runtime_dir(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> str:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `opinion `.
+    :returns: runtime directory tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_dir
+
+
+def user_data_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: data path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_data_path
+
+
+def site_data_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    multipath: bool = False,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param multipath: See `multipath `.
+    :returns: data path shared by users
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_data_path
+
+
+def user_config_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: config path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_config_path
+
+
+def site_config_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    multipath: bool = False,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param multipath: See `roaming `.
+    :returns: config path shared by the users
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, multipath=multipath).site_config_path
+
+
+def user_cache_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `roaming `.
+    :returns: cache path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_cache_path
+
+
+def user_state_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    roaming: bool = False,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param roaming: See `roaming `.
+    :returns: state path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, roaming=roaming).user_state_path
+
+
+def user_log_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `roaming `.
+    :returns: log path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_log_path
+
+
+def user_documents_path() -> Path:
+    """
+    :returns: documents path tied to the user
+    """
+    return PlatformDirs().user_documents_path
+
+
+def user_runtime_path(
+    appname: str | None = None,
+    appauthor: str | None | Literal[False] = None,
+    version: str | None = None,
+    opinion: bool = True,
+) -> Path:
+    """
+    :param appname: See `appname `.
+    :param appauthor: See `appauthor `.
+    :param version: See `version `.
+    :param opinion: See `opinion `.
+    :returns: runtime path tied to the user
+    """
+    return PlatformDirs(appname=appname, appauthor=appauthor, version=version, opinion=opinion).user_runtime_path
+
+
+__all__ = [
+    "__version__",
+    "__version_info__",
+    "PlatformDirs",
+    "AppDirs",
+    "PlatformDirsABC",
+    "user_data_dir",
+    "user_config_dir",
+    "user_cache_dir",
+    "user_state_dir",
+    "user_log_dir",
+    "user_documents_dir",
+    "user_runtime_dir",
+    "site_data_dir",
+    "site_config_dir",
+    "user_data_path",
+    "user_config_path",
+    "user_cache_path",
+    "user_state_path",
+    "user_log_path",
+    "user_documents_path",
+    "user_runtime_path",
+    "site_data_path",
+    "site_config_path",
+]
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__main__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__main__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fc1edd59cf0fa832eb99c22b5b2a4248a26b24e
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__main__.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+from platformdirs import PlatformDirs, __version__
+
+PROPS = (
+    "user_data_dir",
+    "user_config_dir",
+    "user_cache_dir",
+    "user_state_dir",
+    "user_log_dir",
+    "user_documents_dir",
+    "user_runtime_dir",
+    "site_data_dir",
+    "site_config_dir",
+)
+
+
+def main() -> None:
+    app_name = "MyApp"
+    app_author = "MyCompany"
+
+    print(f"-- platformdirs {__version__} --")
+
+    print("-- app dirs (with optional 'version')")
+    dirs = PlatformDirs(app_name, app_author, version="1.0")
+    for prop in PROPS:
+        print(f"{prop}: {getattr(dirs, prop)}")
+
+    print("\n-- app dirs (without optional 'version')")
+    dirs = PlatformDirs(app_name, app_author)
+    for prop in PROPS:
+        print(f"{prop}: {getattr(dirs, prop)}")
+
+    print("\n-- app dirs (without optional 'appauthor')")
+    dirs = PlatformDirs(app_name)
+    for prop in PROPS:
+        print(f"{prop}: {getattr(dirs, prop)}")
+
+    print("\n-- app dirs (with disabled 'appauthor')")
+    dirs = PlatformDirs(app_name, appauthor=False)
+    for prop in PROPS:
+        print(f"{prop}: {getattr(dirs, prop)}")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9e5778df08ffda0e828e61f16567f2ab4b3cbe04
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d098fb75f5735cc5d6f24e4f5ceff61987aee950
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/android.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/android.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aedf3572555b2690f4790b6ed80a731382868b7b
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/android.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/api.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/api.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c7c28a09d1332d27cbd1e4bf847a9ae3b0bf4822
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/api.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fee16de8ac81ede325750ddaad44b54d2d54982d
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c8d1cc5babbdb6abdc0691ade5df9a693e38cc3c
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/version.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/version.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3e6db7e776aa904ea7435ad5e3e0abf18d0c7465
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/version.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..987f33ffbf38dfe4d86adae292f392fb1734c7ea
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/android.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/android.py
new file mode 100644
index 0000000000000000000000000000000000000000..eda80935123cb5db7e18d7fb82fe5f71991d7af8
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/android.py
@@ -0,0 +1,120 @@
+from __future__ import annotations
+
+import os
+import re
+import sys
+from functools import lru_cache
+from typing import cast
+
+from .api import PlatformDirsABC
+
+
+class Android(PlatformDirsABC):
+    """
+    Follows the guidance `from here `_. Makes use of the
+    `appname ` and
+    `version `.
+    """
+
+    @property
+    def user_data_dir(self) -> str:
+        """:return: data directory tied to the user, e.g. ``/data/user///files/``"""
+        return self._append_app_name_and_version(cast(str, _android_folder()), "files")
+
+    @property
+    def site_data_dir(self) -> str:
+        """:return: data directory shared by users, same as `user_data_dir`"""
+        return self.user_data_dir
+
+    @property
+    def user_config_dir(self) -> str:
+        """
+        :return: config directory tied to the user, e.g. ``/data/user///shared_prefs/``
+        """
+        return self._append_app_name_and_version(cast(str, _android_folder()), "shared_prefs")
+
+    @property
+    def site_config_dir(self) -> str:
+        """:return: config directory shared by the users, same as `user_config_dir`"""
+        return self.user_config_dir
+
+    @property
+    def user_cache_dir(self) -> str:
+        """:return: cache directory tied to the user, e.g. e.g. ``/data/user///cache/``"""
+        return self._append_app_name_and_version(cast(str, _android_folder()), "cache")
+
+    @property
+    def user_state_dir(self) -> str:
+        """:return: state directory tied to the user, same as `user_data_dir`"""
+        return self.user_data_dir
+
+    @property
+    def user_log_dir(self) -> str:
+        """
+        :return: log directory tied to the user, same as `user_cache_dir` if not opinionated else ``log`` in it,
+          e.g. ``/data/user///cache//log``
+        """
+        path = self.user_cache_dir
+        if self.opinion:
+            path = os.path.join(path, "log")
+        return path
+
+    @property
+    def user_documents_dir(self) -> str:
+        """
+        :return: documents directory tied to the user e.g. ``/storage/emulated/0/Documents``
+        """
+        return _android_documents_folder()
+
+    @property
+    def user_runtime_dir(self) -> str:
+        """
+        :return: runtime directory tied to the user, same as `user_cache_dir` if not opinionated else ``tmp`` in it,
+          e.g. ``/data/user///cache//tmp``
+        """
+        path = self.user_cache_dir
+        if self.opinion:
+            path = os.path.join(path, "tmp")
+        return path
+
+
+@lru_cache(maxsize=1)
+def _android_folder() -> str | None:
+    """:return: base folder for the Android OS or None if cannot be found"""
+    try:
+        # First try to get path to android app via pyjnius
+        from jnius import autoclass
+
+        Context = autoclass("android.content.Context")  # noqa: N806
+        result: str | None = Context.getFilesDir().getParentFile().getAbsolutePath()
+    except Exception:
+        # if fails find an android folder looking path on the sys.path
+        pattern = re.compile(r"/data/(data|user/\d+)/(.+)/files")
+        for path in sys.path:
+            if pattern.match(path):
+                result = path.split("/files")[0]
+                break
+        else:
+            result = None
+    return result
+
+
+@lru_cache(maxsize=1)
+def _android_documents_folder() -> str:
+    """:return: documents folder for the Android OS"""
+    # Get directories with pyjnius
+    try:
+        from jnius import autoclass
+
+        Context = autoclass("android.content.Context")  # noqa: N806
+        Environment = autoclass("android.os.Environment")  # noqa: N806
+        documents_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath()
+    except Exception:
+        documents_dir = "/storage/emulated/0/Documents"
+
+    return documents_dir
+
+
+__all__ = [
+    "Android",
+]
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/api.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f6e2c2c69d25dba4d1038a2d548fbf68017f91b
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/api.py
@@ -0,0 +1,156 @@
+from __future__ import annotations
+
+import os
+import sys
+from abc import ABC, abstractmethod
+from pathlib import Path
+
+if sys.version_info >= (3, 8):  # pragma: no branch
+    from typing import Literal  # pragma: no cover
+
+
+class PlatformDirsABC(ABC):
+    """
+    Abstract base class for platform directories.
+    """
+
+    def __init__(
+        self,
+        appname: str | None = None,
+        appauthor: str | None | Literal[False] = None,
+        version: str | None = None,
+        roaming: bool = False,
+        multipath: bool = False,
+        opinion: bool = True,
+    ):
+        """
+        Create a new platform directory.
+
+        :param appname: See `appname`.
+        :param appauthor: See `appauthor`.
+        :param version: See `version`.
+        :param roaming: See `roaming`.
+        :param multipath: See `multipath`.
+        :param opinion: See `opinion`.
+        """
+        self.appname = appname  #: The name of application.
+        self.appauthor = appauthor
+        """
+        The name of the app author or distributing body for this application. Typically, it is the owning company name.
+        Defaults to `appname`. You may pass ``False`` to disable it.
+        """
+        self.version = version
+        """
+        An optional version path element to append to the path. You might want to use this if you want multiple versions
+        of your app to be able to run independently. If used, this would typically be ``.``.
+        """
+        self.roaming = roaming
+        """
+        Whether to use the roaming appdata directory on Windows. That means that for users on a Windows network setup
+        for roaming profiles, this user data will be synced on login (see
+        `here `_).
+        """
+        self.multipath = multipath
+        """
+        An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be
+        returned. By default, the first item would only be returned.
+        """
+        self.opinion = opinion  #: A flag to indicating to use opinionated values.
+
+    def _append_app_name_and_version(self, *base: str) -> str:
+        params = list(base[1:])
+        if self.appname:
+            params.append(self.appname)
+            if self.version:
+                params.append(self.version)
+        return os.path.join(base[0], *params)
+
+    @property
+    @abstractmethod
+    def user_data_dir(self) -> str:
+        """:return: data directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def site_data_dir(self) -> str:
+        """:return: data directory shared by users"""
+
+    @property
+    @abstractmethod
+    def user_config_dir(self) -> str:
+        """:return: config directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def site_config_dir(self) -> str:
+        """:return: config directory shared by the users"""
+
+    @property
+    @abstractmethod
+    def user_cache_dir(self) -> str:
+        """:return: cache directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def user_state_dir(self) -> str:
+        """:return: state directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def user_log_dir(self) -> str:
+        """:return: log directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def user_documents_dir(self) -> str:
+        """:return: documents directory tied to the user"""
+
+    @property
+    @abstractmethod
+    def user_runtime_dir(self) -> str:
+        """:return: runtime directory tied to the user"""
+
+    @property
+    def user_data_path(self) -> Path:
+        """:return: data path tied to the user"""
+        return Path(self.user_data_dir)
+
+    @property
+    def site_data_path(self) -> Path:
+        """:return: data path shared by users"""
+        return Path(self.site_data_dir)
+
+    @property
+    def user_config_path(self) -> Path:
+        """:return: config path tied to the user"""
+        return Path(self.user_config_dir)
+
+    @property
+    def site_config_path(self) -> Path:
+        """:return: config path shared by the users"""
+        return Path(self.site_config_dir)
+
+    @property
+    def user_cache_path(self) -> Path:
+        """:return: cache path tied to the user"""
+        return Path(self.user_cache_dir)
+
+    @property
+    def user_state_path(self) -> Path:
+        """:return: state path tied to the user"""
+        return Path(self.user_state_dir)
+
+    @property
+    def user_log_path(self) -> Path:
+        """:return: log path tied to the user"""
+        return Path(self.user_log_dir)
+
+    @property
+    def user_documents_path(self) -> Path:
+        """:return: documents path tied to the user"""
+        return Path(self.user_documents_dir)
+
+    @property
+    def user_runtime_path(self) -> Path:
+        """:return: runtime path tied to the user"""
+        return Path(self.user_runtime_dir)
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/macos.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/macos.py
new file mode 100644
index 0000000000000000000000000000000000000000..a01337c7764e1e1aba1f3ba378a1c9241f31806d
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/macos.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+import os
+
+from .api import PlatformDirsABC
+
+
+class MacOS(PlatformDirsABC):
+    """
+    Platform directories for the macOS operating system. Follows the guidance from `Apple documentation
+    `_.
+    Makes use of the `appname ` and
+    `version `.
+    """
+
+    @property
+    def user_data_dir(self) -> str:
+        """:return: data directory tied to the user, e.g. ``~/Library/Application Support/$appname/$version``"""
+        return self._append_app_name_and_version(os.path.expanduser("~/Library/Application Support/"))
+
+    @property
+    def site_data_dir(self) -> str:
+        """:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``"""
+        return self._append_app_name_and_version("/Library/Application Support")
+
+    @property
+    def user_config_dir(self) -> str:
+        """:return: config directory tied to the user, e.g. ``~/Library/Preferences/$appname/$version``"""
+        return self._append_app_name_and_version(os.path.expanduser("~/Library/Preferences/"))
+
+    @property
+    def site_config_dir(self) -> str:
+        """:return: config directory shared by the users, e.g. ``/Library/Preferences/$appname``"""
+        return self._append_app_name_and_version("/Library/Preferences")
+
+    @property
+    def user_cache_dir(self) -> str:
+        """:return: cache directory tied to the user, e.g. ``~/Library/Caches/$appname/$version``"""
+        return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches"))
+
+    @property
+    def user_state_dir(self) -> str:
+        """:return: state directory tied to the user, same as `user_data_dir`"""
+        return self.user_data_dir
+
+    @property
+    def user_log_dir(self) -> str:
+        """:return: log directory tied to the user, e.g. ``~/Library/Logs/$appname/$version``"""
+        return self._append_app_name_and_version(os.path.expanduser("~/Library/Logs"))
+
+    @property
+    def user_documents_dir(self) -> str:
+        """:return: documents directory tied to the user, e.g. ``~/Documents``"""
+        return os.path.expanduser("~/Documents")
+
+    @property
+    def user_runtime_dir(self) -> str:
+        """:return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
+        return self._append_app_name_and_version(os.path.expanduser("~/Library/Caches/TemporaryItems"))
+
+
+__all__ = [
+    "MacOS",
+]
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/py.typed b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/unix.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/unix.py
new file mode 100644
index 0000000000000000000000000000000000000000..9aca5a030545d3ba26fa96fbfd7cae1c31dcdd15
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/unix.py
@@ -0,0 +1,181 @@
+from __future__ import annotations
+
+import os
+import sys
+from configparser import ConfigParser
+from pathlib import Path
+
+from .api import PlatformDirsABC
+
+if sys.platform.startswith("linux"):  # pragma: no branch # no op check, only to please the type checker
+    from os import getuid
+else:
+
+    def getuid() -> int:
+        raise RuntimeError("should only be used on Linux")
+
+
+class Unix(PlatformDirsABC):
+    """
+    On Unix/Linux, we follow the
+    `XDG Basedir Spec `_. The spec allows
+    overriding directories with environment variables. The examples show are the default values, alongside the name of
+    the environment variable that overrides them. Makes use of the
+    `appname `,
+    `version `,
+    `multipath `,
+    `opinion `.
+    """
+
+    @property
+    def user_data_dir(self) -> str:
+        """
+        :return: data directory tied to the user, e.g. ``~/.local/share/$appname/$version`` or
+         ``$XDG_DATA_HOME/$appname/$version``
+        """
+        path = os.environ.get("XDG_DATA_HOME", "")
+        if not path.strip():
+            path = os.path.expanduser("~/.local/share")
+        return self._append_app_name_and_version(path)
+
+    @property
+    def site_data_dir(self) -> str:
+        """
+        :return: data directories shared by users (if `multipath ` is
+         enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
+         path separator), e.g. ``/usr/local/share/$appname/$version`` or ``/usr/share/$appname/$version``
+        """
+        # XDG default for $XDG_DATA_DIRS; only first, if multipath is False
+        path = os.environ.get("XDG_DATA_DIRS", "")
+        if not path.strip():
+            path = f"/usr/local/share{os.pathsep}/usr/share"
+        return self._with_multi_path(path)
+
+    def _with_multi_path(self, path: str) -> str:
+        path_list = path.split(os.pathsep)
+        if not self.multipath:
+            path_list = path_list[0:1]
+        path_list = [self._append_app_name_and_version(os.path.expanduser(p)) for p in path_list]
+        return os.pathsep.join(path_list)
+
+    @property
+    def user_config_dir(self) -> str:
+        """
+        :return: config directory tied to the user, e.g. ``~/.config/$appname/$version`` or
+         ``$XDG_CONFIG_HOME/$appname/$version``
+        """
+        path = os.environ.get("XDG_CONFIG_HOME", "")
+        if not path.strip():
+            path = os.path.expanduser("~/.config")
+        return self._append_app_name_and_version(path)
+
+    @property
+    def site_config_dir(self) -> str:
+        """
+        :return: config directories shared by users (if `multipath `
+         is enabled and ``XDG_DATA_DIR`` is set and a multi path the response is also a multi path separated by the OS
+         path separator), e.g. ``/etc/xdg/$appname/$version``
+        """
+        # XDG default for $XDG_CONFIG_DIRS only first, if multipath is False
+        path = os.environ.get("XDG_CONFIG_DIRS", "")
+        if not path.strip():
+            path = "/etc/xdg"
+        return self._with_multi_path(path)
+
+    @property
+    def user_cache_dir(self) -> str:
+        """
+        :return: cache directory tied to the user, e.g. ``~/.cache/$appname/$version`` or
+         ``~/$XDG_CACHE_HOME/$appname/$version``
+        """
+        path = os.environ.get("XDG_CACHE_HOME", "")
+        if not path.strip():
+            path = os.path.expanduser("~/.cache")
+        return self._append_app_name_and_version(path)
+
+    @property
+    def user_state_dir(self) -> str:
+        """
+        :return: state directory tied to the user, e.g. ``~/.local/state/$appname/$version`` or
+         ``$XDG_STATE_HOME/$appname/$version``
+        """
+        path = os.environ.get("XDG_STATE_HOME", "")
+        if not path.strip():
+            path = os.path.expanduser("~/.local/state")
+        return self._append_app_name_and_version(path)
+
+    @property
+    def user_log_dir(self) -> str:
+        """
+        :return: log directory tied to the user, same as `user_state_dir` if not opinionated else ``log`` in it
+        """
+        path = self.user_state_dir
+        if self.opinion:
+            path = os.path.join(path, "log")
+        return path
+
+    @property
+    def user_documents_dir(self) -> str:
+        """
+        :return: documents directory tied to the user, e.g. ``~/Documents``
+        """
+        documents_dir = _get_user_dirs_folder("XDG_DOCUMENTS_DIR")
+        if documents_dir is None:
+            documents_dir = os.environ.get("XDG_DOCUMENTS_DIR", "").strip()
+            if not documents_dir:
+                documents_dir = os.path.expanduser("~/Documents")
+
+        return documents_dir
+
+    @property
+    def user_runtime_dir(self) -> str:
+        """
+        :return: runtime directory tied to the user, e.g. ``/run/user/$(id -u)/$appname/$version`` or
+         ``$XDG_RUNTIME_DIR/$appname/$version``
+        """
+        path = os.environ.get("XDG_RUNTIME_DIR", "")
+        if not path.strip():
+            path = f"/run/user/{getuid()}"
+        return self._append_app_name_and_version(path)
+
+    @property
+    def site_data_path(self) -> Path:
+        """:return: data path shared by users. Only return first item, even if ``multipath`` is set to ``True``"""
+        return self._first_item_as_path_if_multipath(self.site_data_dir)
+
+    @property
+    def site_config_path(self) -> Path:
+        """:return: config path shared by the users. Only return first item, even if ``multipath`` is set to ``True``"""
+        return self._first_item_as_path_if_multipath(self.site_config_dir)
+
+    def _first_item_as_path_if_multipath(self, directory: str) -> Path:
+        if self.multipath:
+            # If multipath is True, the first path is returned.
+            directory = directory.split(os.pathsep)[0]
+        return Path(directory)
+
+
+def _get_user_dirs_folder(key: str) -> str | None:
+    """Return directory from user-dirs.dirs config file. See https://freedesktop.org/wiki/Software/xdg-user-dirs/"""
+    user_dirs_config_path = os.path.join(Unix().user_config_dir, "user-dirs.dirs")
+    if os.path.exists(user_dirs_config_path):
+        parser = ConfigParser()
+
+        with open(user_dirs_config_path) as stream:
+            # Add fake section header, so ConfigParser doesn't complain
+            parser.read_string(f"[top]\n{stream.read()}")
+
+        if key not in parser["top"]:
+            return None
+
+        path = parser["top"][key].strip('"')
+        # Handle relative home paths
+        path = path.replace("$HOME", os.path.expanduser("~"))
+        return path
+
+    return None
+
+
+__all__ = [
+    "Unix",
+]
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/version.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/version.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f6eb98e8f0ef41d6fab05af6e9c24c5ef8a04b8
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/version.py
@@ -0,0 +1,4 @@
+# file generated by setuptools_scm
+# don't change, don't track in version control
+__version__ = version = '2.6.2'
+__version_tuple__ = version_tuple = (2, 6, 2)
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/windows.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/windows.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5c27b341403fa03283562373b2a0b05daba075e
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/platformdirs/windows.py
@@ -0,0 +1,184 @@
+from __future__ import annotations
+
+import ctypes
+import os
+import sys
+from functools import lru_cache
+from typing import Callable
+
+from .api import PlatformDirsABC
+
+
+class Windows(PlatformDirsABC):
+    """`MSDN on where to store app data files
+    `_.
+    Makes use of the
+    `appname `,
+    `appauthor `,
+    `version `,
+    `roaming `,
+    `opinion `."""
+
+    @property
+    def user_data_dir(self) -> str:
+        """
+        :return: data directory tied to the user, e.g.
+         ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname`` (not roaming) or
+         ``%USERPROFILE%\\AppData\\Roaming\\$appauthor\\$appname`` (roaming)
+        """
+        const = "CSIDL_APPDATA" if self.roaming else "CSIDL_LOCAL_APPDATA"
+        path = os.path.normpath(get_win_folder(const))
+        return self._append_parts(path)
+
+    def _append_parts(self, path: str, *, opinion_value: str | None = None) -> str:
+        params = []
+        if self.appname:
+            if self.appauthor is not False:
+                author = self.appauthor or self.appname
+                params.append(author)
+            params.append(self.appname)
+            if opinion_value is not None and self.opinion:
+                params.append(opinion_value)
+            if self.version:
+                params.append(self.version)
+        return os.path.join(path, *params)
+
+    @property
+    def site_data_dir(self) -> str:
+        """:return: data directory shared by users, e.g. ``C:\\ProgramData\\$appauthor\\$appname``"""
+        path = os.path.normpath(get_win_folder("CSIDL_COMMON_APPDATA"))
+        return self._append_parts(path)
+
+    @property
+    def user_config_dir(self) -> str:
+        """:return: config directory tied to the user, same as `user_data_dir`"""
+        return self.user_data_dir
+
+    @property
+    def site_config_dir(self) -> str:
+        """:return: config directory shared by the users, same as `site_data_dir`"""
+        return self.site_data_dir
+
+    @property
+    def user_cache_dir(self) -> str:
+        """
+        :return: cache directory tied to the user (if opinionated with ``Cache`` folder within ``$appname``) e.g.
+         ``%USERPROFILE%\\AppData\\Local\\$appauthor\\$appname\\Cache\\$version``
+        """
+        path = os.path.normpath(get_win_folder("CSIDL_LOCAL_APPDATA"))
+        return self._append_parts(path, opinion_value="Cache")
+
+    @property
+    def user_state_dir(self) -> str:
+        """:return: state directory tied to the user, same as `user_data_dir`"""
+        return self.user_data_dir
+
+    @property
+    def user_log_dir(self) -> str:
+        """
+        :return: log directory tied to the user, same as `user_data_dir` if not opinionated else ``Logs`` in it
+        """
+        path = self.user_data_dir
+        if self.opinion:
+            path = os.path.join(path, "Logs")
+        return path
+
+    @property
+    def user_documents_dir(self) -> str:
+        """
+        :return: documents directory tied to the user e.g. ``%USERPROFILE%\\Documents``
+        """
+        return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))
+
+    @property
+    def user_runtime_dir(self) -> str:
+        """
+        :return: runtime directory tied to the user, e.g.
+         ``%USERPROFILE%\\AppData\\Local\\Temp\\$appauthor\\$appname``
+        """
+        path = os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Temp"))
+        return self._append_parts(path)
+
+
+def get_win_folder_from_env_vars(csidl_name: str) -> str:
+    """Get folder from environment variables."""
+    if csidl_name == "CSIDL_PERSONAL":  # does not have an environment name
+        return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")
+
+    env_var_name = {
+        "CSIDL_APPDATA": "APPDATA",
+        "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
+        "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA",
+    }.get(csidl_name)
+    if env_var_name is None:
+        raise ValueError(f"Unknown CSIDL name: {csidl_name}")
+    result = os.environ.get(env_var_name)
+    if result is None:
+        raise ValueError(f"Unset environment variable: {env_var_name}")
+    return result
+
+
+def get_win_folder_from_registry(csidl_name: str) -> str:
+    """Get folder from the registry.
+
+    This is a fallback technique at best. I'm not sure if using the
+    registry for this guarantees us the correct answer for all CSIDL_*
+    names.
+    """
+    shell_folder_name = {
+        "CSIDL_APPDATA": "AppData",
+        "CSIDL_COMMON_APPDATA": "Common AppData",
+        "CSIDL_LOCAL_APPDATA": "Local AppData",
+        "CSIDL_PERSONAL": "Personal",
+    }.get(csidl_name)
+    if shell_folder_name is None:
+        raise ValueError(f"Unknown CSIDL name: {csidl_name}")
+    if sys.platform != "win32":  # only needed for mypy type checker to know that this code runs only on Windows
+        raise NotImplementedError
+    import winreg
+
+    key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders")
+    directory, _ = winreg.QueryValueEx(key, shell_folder_name)
+    return str(directory)
+
+
+def get_win_folder_via_ctypes(csidl_name: str) -> str:
+    """Get folder with ctypes."""
+    csidl_const = {
+        "CSIDL_APPDATA": 26,
+        "CSIDL_COMMON_APPDATA": 35,
+        "CSIDL_LOCAL_APPDATA": 28,
+        "CSIDL_PERSONAL": 5,
+    }.get(csidl_name)
+    if csidl_const is None:
+        raise ValueError(f"Unknown CSIDL name: {csidl_name}")
+
+    buf = ctypes.create_unicode_buffer(1024)
+    windll = getattr(ctypes, "windll")  # noqa: B009 # using getattr to avoid false positive with mypy type checker
+    windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
+
+    # Downgrade to short path name if it has highbit chars.
+    if any(ord(c) > 255 for c in buf):
+        buf2 = ctypes.create_unicode_buffer(1024)
+        if windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
+            buf = buf2
+
+    return buf.value
+
+
+def _pick_get_win_folder() -> Callable[[str], str]:
+    if hasattr(ctypes, "windll"):
+        return get_win_folder_via_ctypes
+    try:
+        import winreg  # noqa: F401
+    except ImportError:
+        return get_win_folder_from_env_vars
+    else:
+        return get_win_folder_from_registry
+
+
+get_win_folder = lru_cache(maxsize=None)(_pick_get_win_folder())
+
+__all__ = [
+    "Windows",
+]
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/zipp.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/zipp.py
new file mode 100644
index 0000000000000000000000000000000000000000..26b723c1fd3e25740e0268b8c9b50905c58c3d4a
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/_vendor/zipp.py
@@ -0,0 +1,329 @@
+import io
+import posixpath
+import zipfile
+import itertools
+import contextlib
+import sys
+import pathlib
+
+if sys.version_info < (3, 7):
+    from collections import OrderedDict
+else:
+    OrderedDict = dict
+
+
+__all__ = ['Path']
+
+
+def _parents(path):
+    """
+    Given a path with elements separated by
+    posixpath.sep, generate all parents of that path.
+
+    >>> list(_parents('b/d'))
+    ['b']
+    >>> list(_parents('/b/d/'))
+    ['/b']
+    >>> list(_parents('b/d/f/'))
+    ['b/d', 'b']
+    >>> list(_parents('b'))
+    []
+    >>> list(_parents(''))
+    []
+    """
+    return itertools.islice(_ancestry(path), 1, None)
+
+
+def _ancestry(path):
+    """
+    Given a path with elements separated by
+    posixpath.sep, generate all elements of that path
+
+    >>> list(_ancestry('b/d'))
+    ['b/d', 'b']
+    >>> list(_ancestry('/b/d/'))
+    ['/b/d', '/b']
+    >>> list(_ancestry('b/d/f/'))
+    ['b/d/f', 'b/d', 'b']
+    >>> list(_ancestry('b'))
+    ['b']
+    >>> list(_ancestry(''))
+    []
+    """
+    path = path.rstrip(posixpath.sep)
+    while path and path != posixpath.sep:
+        yield path
+        path, tail = posixpath.split(path)
+
+
+_dedupe = OrderedDict.fromkeys
+"""Deduplicate an iterable in original order"""
+
+
+def _difference(minuend, subtrahend):
+    """
+    Return items in minuend not in subtrahend, retaining order
+    with O(1) lookup.
+    """
+    return itertools.filterfalse(set(subtrahend).__contains__, minuend)
+
+
+class CompleteDirs(zipfile.ZipFile):
+    """
+    A ZipFile subclass that ensures that implied directories
+    are always included in the namelist.
+    """
+
+    @staticmethod
+    def _implied_dirs(names):
+        parents = itertools.chain.from_iterable(map(_parents, names))
+        as_dirs = (p + posixpath.sep for p in parents)
+        return _dedupe(_difference(as_dirs, names))
+
+    def namelist(self):
+        names = super(CompleteDirs, self).namelist()
+        return names + list(self._implied_dirs(names))
+
+    def _name_set(self):
+        return set(self.namelist())
+
+    def resolve_dir(self, name):
+        """
+        If the name represents a directory, return that name
+        as a directory (with the trailing slash).
+        """
+        names = self._name_set()
+        dirname = name + '/'
+        dir_match = name not in names and dirname in names
+        return dirname if dir_match else name
+
+    @classmethod
+    def make(cls, source):
+        """
+        Given a source (filename or zipfile), return an
+        appropriate CompleteDirs subclass.
+        """
+        if isinstance(source, CompleteDirs):
+            return source
+
+        if not isinstance(source, zipfile.ZipFile):
+            return cls(_pathlib_compat(source))
+
+        # Only allow for FastLookup when supplied zipfile is read-only
+        if 'r' not in source.mode:
+            cls = CompleteDirs
+
+        source.__class__ = cls
+        return source
+
+
+class FastLookup(CompleteDirs):
+    """
+    ZipFile subclass to ensure implicit
+    dirs exist and are resolved rapidly.
+    """
+
+    def namelist(self):
+        with contextlib.suppress(AttributeError):
+            return self.__names
+        self.__names = super(FastLookup, self).namelist()
+        return self.__names
+
+    def _name_set(self):
+        with contextlib.suppress(AttributeError):
+            return self.__lookup
+        self.__lookup = super(FastLookup, self)._name_set()
+        return self.__lookup
+
+
+def _pathlib_compat(path):
+    """
+    For path-like objects, convert to a filename for compatibility
+    on Python 3.6.1 and earlier.
+    """
+    try:
+        return path.__fspath__()
+    except AttributeError:
+        return str(path)
+
+
+class Path:
+    """
+    A pathlib-compatible interface for zip files.
+
+    Consider a zip file with this structure::
+
+        .
+        ├── a.txt
+        └── b
+            ├── c.txt
+            └── d
+                └── e.txt
+
+    >>> data = io.BytesIO()
+    >>> zf = zipfile.ZipFile(data, 'w')
+    >>> zf.writestr('a.txt', 'content of a')
+    >>> zf.writestr('b/c.txt', 'content of c')
+    >>> zf.writestr('b/d/e.txt', 'content of e')
+    >>> zf.filename = 'mem/abcde.zip'
+
+    Path accepts the zipfile object itself or a filename
+
+    >>> root = Path(zf)
+
+    From there, several path operations are available.
+
+    Directory iteration (including the zip file itself):
+
+    >>> a, b = root.iterdir()
+    >>> a
+    Path('mem/abcde.zip', 'a.txt')
+    >>> b
+    Path('mem/abcde.zip', 'b/')
+
+    name property:
+
+    >>> b.name
+    'b'
+
+    join with divide operator:
+
+    >>> c = b / 'c.txt'
+    >>> c
+    Path('mem/abcde.zip', 'b/c.txt')
+    >>> c.name
+    'c.txt'
+
+    Read text:
+
+    >>> c.read_text()
+    'content of c'
+
+    existence:
+
+    >>> c.exists()
+    True
+    >>> (b / 'missing.txt').exists()
+    False
+
+    Coercion to string:
+
+    >>> import os
+    >>> str(c).replace(os.sep, posixpath.sep)
+    'mem/abcde.zip/b/c.txt'
+
+    At the root, ``name``, ``filename``, and ``parent``
+    resolve to the zipfile. Note these attributes are not
+    valid and will raise a ``ValueError`` if the zipfile
+    has no filename.
+
+    >>> root.name
+    'abcde.zip'
+    >>> str(root.filename).replace(os.sep, posixpath.sep)
+    'mem/abcde.zip'
+    >>> str(root.parent)
+    'mem'
+    """
+
+    __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
+
+    def __init__(self, root, at=""):
+        """
+        Construct a Path from a ZipFile or filename.
+
+        Note: When the source is an existing ZipFile object,
+        its type (__class__) will be mutated to a
+        specialized type. If the caller wishes to retain the
+        original type, the caller should either create a
+        separate ZipFile object or pass a filename.
+        """
+        self.root = FastLookup.make(root)
+        self.at = at
+
+    def open(self, mode='r', *args, pwd=None, **kwargs):
+        """
+        Open this entry as text or binary following the semantics
+        of ``pathlib.Path.open()`` by passing arguments through
+        to io.TextIOWrapper().
+        """
+        if self.is_dir():
+            raise IsADirectoryError(self)
+        zip_mode = mode[0]
+        if not self.exists() and zip_mode == 'r':
+            raise FileNotFoundError(self)
+        stream = self.root.open(self.at, zip_mode, pwd=pwd)
+        if 'b' in mode:
+            if args or kwargs:
+                raise ValueError("encoding args invalid for binary operation")
+            return stream
+        return io.TextIOWrapper(stream, *args, **kwargs)
+
+    @property
+    def name(self):
+        return pathlib.Path(self.at).name or self.filename.name
+
+    @property
+    def suffix(self):
+        return pathlib.Path(self.at).suffix or self.filename.suffix
+
+    @property
+    def suffixes(self):
+        return pathlib.Path(self.at).suffixes or self.filename.suffixes
+
+    @property
+    def stem(self):
+        return pathlib.Path(self.at).stem or self.filename.stem
+
+    @property
+    def filename(self):
+        return pathlib.Path(self.root.filename).joinpath(self.at)
+
+    def read_text(self, *args, **kwargs):
+        with self.open('r', *args, **kwargs) as strm:
+            return strm.read()
+
+    def read_bytes(self):
+        with self.open('rb') as strm:
+            return strm.read()
+
+    def _is_child(self, path):
+        return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/")
+
+    def _next(self, at):
+        return self.__class__(self.root, at)
+
+    def is_dir(self):
+        return not self.at or self.at.endswith("/")
+
+    def is_file(self):
+        return self.exists() and not self.is_dir()
+
+    def exists(self):
+        return self.at in self.root._name_set()
+
+    def iterdir(self):
+        if not self.is_dir():
+            raise ValueError("Can't listdir a file")
+        subs = map(self._next, self.root.namelist())
+        return filter(self._is_child, subs)
+
+    def __str__(self):
+        return posixpath.join(self.root.filename, self.at)
+
+    def __repr__(self):
+        return self.__repr.format(self=self)
+
+    def joinpath(self, *other):
+        next = posixpath.join(self.at, *map(_pathlib_compat, other))
+        return self._next(self.root.resolve_dir(next))
+
+    __truediv__ = joinpath
+
+    @property
+    def parent(self):
+        if not self.at:
+            return self.filename.parent
+        parent_at = posixpath.dirname(self.at.rstrip('/'))
+        if parent_at:
+            parent_at += '/'
+        return self._next(parent_at)
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..daa978ff728e28ea61611b0551c7acb6a738f19e
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__init__.py
@@ -0,0 +1,104 @@
+from __future__ import annotations
+from importlib.machinery import ModuleSpec
+import importlib.util
+import sys
+from types import ModuleType
+from typing import Iterable, Sequence
+
+
+class VendorImporter:
+    """
+    A PEP 302 meta path importer for finding optionally-vendored
+    or otherwise naturally-installed packages from root_name.
+    """
+
+    def __init__(
+        self,
+        root_name: str,
+        vendored_names: Iterable[str] = (),
+        vendor_pkg: str | None = None,
+    ):
+        self.root_name = root_name
+        self.vendored_names = set(vendored_names)
+        self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor')
+
+    @property
+    def search_path(self):
+        """
+        Search first the vendor package then as a natural package.
+        """
+        yield self.vendor_pkg + '.'
+        yield ''
+
+    def _module_matches_namespace(self, fullname):
+        """Figure out if the target module is vendored."""
+        root, base, target = fullname.partition(self.root_name + '.')
+        return not root and any(map(target.startswith, self.vendored_names))
+
+    def load_module(self, fullname: str):
+        """
+        Iterate over the search path to locate and load fullname.
+        """
+        root, base, target = fullname.partition(self.root_name + '.')
+        for prefix in self.search_path:
+            extant = prefix + target
+            try:
+                __import__(extant)
+            except ImportError:
+                continue
+            mod = sys.modules[extant]
+            sys.modules[fullname] = mod
+            return mod
+        else:
+            raise ImportError(
+                "The '{target}' package is required; "
+                "normally this is bundled with this package so if you get "
+                "this warning, consult the packager of your "
+                "distribution.".format(**locals())
+            )
+
+    def create_module(self, spec: ModuleSpec):
+        return self.load_module(spec.name)
+
+    def exec_module(self, module: ModuleType):
+        pass
+
+    def find_spec(
+        self,
+        fullname: str,
+        path: Sequence[str] | None = None,
+        target: ModuleType | None = None,
+    ):
+        """Return a module spec for vendored names."""
+        return (
+            # This should fix itself next mypy release https://github.com/python/typeshed/pull/11890
+            importlib.util.spec_from_loader(fullname, self)  # type: ignore[arg-type]
+            if self._module_matches_namespace(fullname)
+            else None
+        )
+
+    def install(self):
+        """
+        Install this importer into sys.meta_path if not already present.
+        """
+        if self not in sys.meta_path:
+            sys.meta_path.append(self)
+
+
+# [[[cog
+# import cog
+# from tools.vendored import yield_top_level
+# names = "\n".join(f"    {x!r}," for x in yield_top_level('pkg_resources'))
+# cog.outl(f"names = (\n{names}\n)")
+# ]]]
+names = (
+    'backports',
+    'importlib_resources',
+    'jaraco',
+    'more_itertools',
+    'packaging',
+    'platformdirs',
+    'zipp',
+)
+# [[[end]]]
+VendorImporter(__name__, names).install()
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..735aacabeddbf91b2a61e22c98caf8d565a59650
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/pkg_resources/extern/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..64fbfecbdf8f84dd4aa4a5d318a95b7cdb70a47f
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..12ada5dbda08f28a2ffc863cb502bd3f25455ded
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.py
@@ -0,0 +1,15 @@
+from .. import decoders
+
+
+Decoder = decoders.Decoder
+ByteLevel = decoders.ByteLevel
+Replace = decoders.Replace
+WordPiece = decoders.WordPiece
+ByteFallback = decoders.ByteFallback
+Fuse = decoders.Fuse
+Strip = decoders.Strip
+Metaspace = decoders.Metaspace
+BPEDecoder = decoders.BPEDecoder
+CTC = decoders.CTC
+Sequence = decoders.Sequence
+DecodeStream = decoders.DecodeStream
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..29fb501c994fccb325192bdfcaf98d3e81d35cd7
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__init__.pyi
@@ -0,0 +1,569 @@
+# Generated content DO NOT EDIT
+class DecodeStream:
+    """
+    Class needed for streaming decode
+
+    """
+    def __init__(self, ids=None, skip_special_tokens=False):
+        pass
+
+    def __getstate__(self, /):
+        """
+        Helper for pickle.
+        """
+        pass
+
+    def step(self, tokenizer, id):
+        """
+        Streaming decode step
+
+        Args:
+            tokenizer (:class:`~tokenizers.Tokenizer`):
+               The tokenizer to use for decoding
+           id (:obj:`int` or `List[int]`):
+              The next token id or list of token ids to add to the stream
+
+
+        Returns:
+            :obj:`Optional[str]`: The next decoded string chunk, or None if not enough
+                tokens have been provided yet.
+        """
+        pass
+
+class Decoder:
+    """
+    Base class for all decoders
+
+    This class is not supposed to be instantiated directly. Instead, any implementation of
+    a Decoder will return an instance of this class when instantiated.
+    """
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class BPEDecoder(Decoder):
+    """
+    BPEDecoder Decoder
+
+    Args:
+        suffix (:obj:`str`, `optional`, defaults to :obj:``):
+            The suffix that was used to characterize an end-of-word. This suffix will
+            be replaced by whitespaces during the decoding
+    """
+    def __init__(self, suffix=""):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+    @property
+    def suffix(self):
+        """ """
+        pass
+
+    @suffix.setter
+    def suffix(self, value):
+        """ """
+        pass
+
+class ByteFallback(Decoder):
+    """
+    ByteFallback Decoder
+    ByteFallback is a simple trick which converts tokens looking like `<0x61>`
+    to pure bytes, and attempts to make them into a string. If the tokens
+    cannot be decoded you will get � instead for each inconvertible byte token
+
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class ByteLevel(Decoder):
+    """
+    ByteLevel Decoder
+
+    This decoder is to be used in tandem with the :class:`~tokenizers.pre_tokenizers.ByteLevel`
+    :class:`~tokenizers.pre_tokenizers.PreTokenizer`.
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class CTC(Decoder):
+    """
+    CTC Decoder
+
+    Args:
+        pad_token (:obj:`str`, `optional`, defaults to :obj:``):
+            The pad token used by CTC to delimit a new token.
+        word_delimiter_token (:obj:`str`, `optional`, defaults to :obj:`|`):
+            The word delimiter token. It will be replaced by a 
+        cleanup (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to cleanup some tokenization artifacts.
+            Mainly spaces before punctuation, and some abbreviated english forms.
+    """
+    def __init__(self, pad_token="", word_delimiter_token="|", cleanup=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def cleanup(self):
+        """ """
+        pass
+
+    @cleanup.setter
+    def cleanup(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+    @property
+    def pad_token(self):
+        """ """
+        pass
+
+    @pad_token.setter
+    def pad_token(self, value):
+        """ """
+        pass
+
+    @property
+    def word_delimiter_token(self):
+        """ """
+        pass
+
+    @word_delimiter_token.setter
+    def word_delimiter_token(self, value):
+        """ """
+        pass
+
+class Fuse(Decoder):
+    """
+    Fuse Decoder
+    Fuse simply fuses every token into a single string.
+    This is the last step of decoding, this decoder exists only if
+    there is need to add other decoders *after* the fusion
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class Metaspace(Decoder):
+    """
+    Metaspace Decoder
+
+    Args:
+        replacement (:obj:`str`, `optional`, defaults to :obj:`▁`):
+            The replacement character. Must be exactly one character. By default we
+            use the `▁` (U+2581) meta symbol (Same as in SentencePiece).
+
+        prepend_scheme (:obj:`str`, `optional`, defaults to :obj:`"always"`):
+            Whether to add a space to the first word if there isn't already one. This
+            lets us treat `hello` exactly like `say hello`.
+            Choices: "always", "never", "first". First means the space is only added on the first
+            token (relevant when special tokens are used or other pre_tokenizer are used).
+    """
+    def __init__(self, replacement="▁", prepend_scheme="always", split=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+    @property
+    def prepend_scheme(self):
+        """ """
+        pass
+
+    @prepend_scheme.setter
+    def prepend_scheme(self, value):
+        """ """
+        pass
+
+    @property
+    def replacement(self):
+        """ """
+        pass
+
+    @replacement.setter
+    def replacement(self, value):
+        """ """
+        pass
+
+    @property
+    def split(self):
+        """ """
+        pass
+
+    @split.setter
+    def split(self, value):
+        """ """
+        pass
+
+class Replace(Decoder):
+    """
+    Replace Decoder
+
+    This decoder is to be used in tandem with the :class:`~tokenizers.pre_tokenizers.Replace`
+    :class:`~tokenizers.pre_tokenizers.PreTokenizer`.
+    """
+    def __init__(self, pattern, content):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class Sequence(Decoder):
+    """
+    Sequence Decoder
+
+    Args:
+        decoders (:obj:`List[Decoder]`)
+            The decoders that need to be chained
+    """
+    def __init__(self, decoders):
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+class Strip(Decoder):
+    """
+    Strip normalizer
+    Strips n left characters of each token, or n right characters of each token
+    """
+    def __init__(self, content=" ", left=0, right=0):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def content(self):
+        """ """
+        pass
+
+    @content.setter
+    def content(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+    @property
+    def start(self):
+        """ """
+        pass
+
+    @start.setter
+    def start(self, value):
+        """ """
+        pass
+
+    @property
+    def stop(self):
+        """ """
+        pass
+
+    @stop.setter
+    def stop(self, value):
+        """ """
+        pass
+
+class WordPiece(Decoder):
+    """
+    WordPiece Decoder
+
+    Args:
+        prefix (:obj:`str`, `optional`, defaults to :obj:`##`):
+            The prefix to use for subwords that are not a beginning-of-word
+
+        cleanup (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to cleanup some tokenization artifacts. Mainly spaces before punctuation,
+            and some abbreviated english forms.
+    """
+    def __init__(self, prefix="##", cleanup=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def cleanup(self):
+        """ """
+        pass
+
+    @cleanup.setter
+    def cleanup(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(decoder):
+        """ """
+        pass
+
+    def decode(self, tokens):
+        """
+        Decode the given list of tokens to a final string
+
+        Args:
+            tokens (:obj:`List[str]`):
+                The list of tokens to decode
+
+        Returns:
+            :obj:`str`: The decoded string
+        """
+        pass
+
+    @property
+    def prefix(self):
+        """ """
+        pass
+
+    @prefix.setter
+    def prefix(self, value):
+        """ """
+        pass
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5730eadf1f8529b7b7d206cb20a21099bfcc3e02
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/decoders/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e775892d04a91d645653ea9015954b7985d3147
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__init__.py
@@ -0,0 +1,6 @@
+from .base_tokenizer import BaseTokenizer
+from .bert_wordpiece import BertWordPieceTokenizer
+from .byte_level_bpe import ByteLevelBPETokenizer
+from .char_level_bpe import CharBPETokenizer
+from .sentencepiece_bpe import SentencePieceBPETokenizer
+from .sentencepiece_unigram import SentencePieceUnigramTokenizer
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1b169afe374fb48bdb64d297a71e79b5280db215
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/base_tokenizer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/base_tokenizer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..820a371703bb3a86a1889670a83a471bfebc8003
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/base_tokenizer.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/bert_wordpiece.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/bert_wordpiece.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1d2fe48386716ceba330475fc5e555a8175eea48
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/bert_wordpiece.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/byte_level_bpe.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/byte_level_bpe.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e84b95c7c9829d05a3107b1a66d8432b6c130a44
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/byte_level_bpe.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/char_level_bpe.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/char_level_bpe.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8c684ebd4e5b5c698275a9524f1cc41f575b0904
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/char_level_bpe.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_bpe.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_bpe.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d2a79bb6ce919e9271376c8f2e858df83443c02f
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_bpe.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_unigram.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_unigram.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9ffed3587519003bf4c8b3ecc79c5851d6568a6e
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/__pycache__/sentencepiece_unigram.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/base_tokenizer.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/base_tokenizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2e7effb4cfeeef4a4cf060ebcfdd4a4c420a7a4
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/base_tokenizer.py
@@ -0,0 +1,459 @@
+from typing import Dict, List, Optional, Tuple, Union
+
+from tokenizers import AddedToken, EncodeInput, Encoding, InputSequence, Tokenizer
+from tokenizers.decoders import Decoder
+from tokenizers.models import Model
+from tokenizers.normalizers import Normalizer
+from tokenizers.pre_tokenizers import PreTokenizer
+from tokenizers.processors import PostProcessor
+
+
+Offsets = Tuple[int, int]
+
+
+class BaseTokenizer:
+    def __init__(self, tokenizer: Tokenizer, parameters=None):
+        self._tokenizer = tokenizer
+        self._parameters = parameters if parameters is not None else {}
+
+    def __repr__(self):
+        return "Tokenizer(vocabulary_size={}, {})".format(
+            self._tokenizer.get_vocab_size(),
+            ", ".join(k + "=" + str(v) for k, v in self._parameters.items()),
+        )
+
+    def num_special_tokens_to_add(self, is_pair: bool) -> int:
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+        :param is_pair: Boolean indicating if the input would be a single sentence or a pair
+        :return:
+        """
+        return self._tokenizer.num_special_tokens_to_add(is_pair)
+
+    def get_vocab(self, with_added_tokens: bool = True) -> Dict[str, int]:
+        """Returns the vocabulary
+
+        Args:
+            with_added_tokens: boolean:
+                Whether to include the added tokens in the vocabulary
+
+        Returns:
+            The vocabulary
+        """
+        return self._tokenizer.get_vocab(with_added_tokens=with_added_tokens)
+
+    def get_added_tokens_decoder(self) -> Dict[int, AddedToken]:
+        """Returns the added reverse vocabulary
+
+        Returns:
+            The added vocabulary mapping ints to AddedTokens
+        """
+        return self._tokenizer.get_added_tokens_decoder()
+
+    def get_vocab_size(self, with_added_tokens: bool = True) -> int:
+        """Return the size of vocabulary, with or without added tokens.
+
+        Args:
+            with_added_tokens: (`optional`) bool:
+                Whether to count in added special tokens or not
+
+        Returns:
+            Size of vocabulary
+        """
+        return self._tokenizer.get_vocab_size(with_added_tokens=with_added_tokens)
+
+    def enable_padding(
+        self,
+        direction: Optional[str] = "right",
+        pad_to_multiple_of: Optional[int] = None,
+        pad_id: Optional[int] = 0,
+        pad_type_id: Optional[int] = 0,
+        pad_token: Optional[str] = "[PAD]",
+        length: Optional[int] = None,
+    ):
+        """Change the padding strategy
+
+        Args:
+            direction: (`optional`) str:
+                Can be one of: `right` or `left`
+
+            pad_to_multiple_of: (`optional`) unsigned int:
+                If specified, the padding length should always snap to the next multiple of
+                the given value. For example if we were going to pad with a length of 250 but
+                `pad_to_multiple_of=8` then we will pad to 256.
+
+            pad_id: (`optional`) unsigned int:
+                The indice to be used when padding
+
+            pad_type_id: (`optional`) unsigned int:
+                The type indice to be used when padding
+
+            pad_token: (`optional`) str:
+                The pad token to be used when padding
+
+            length: (`optional`) unsigned int:
+                If specified, the length at which to pad. If not specified
+                we pad using the size of the longest sequence in a batch
+        """
+        return self._tokenizer.enable_padding(
+            direction=direction,
+            pad_to_multiple_of=pad_to_multiple_of,
+            pad_id=pad_id,
+            pad_type_id=pad_type_id,
+            pad_token=pad_token,
+            length=length,
+        )
+
+    def no_padding(self):
+        """Disable padding"""
+        return self._tokenizer.no_padding()
+
+    @property
+    def padding(self) -> Optional[dict]:
+        """Get the current padding parameters
+
+        Returns:
+            None if padding is disabled, a dict with the currently set parameters
+            if the padding is enabled.
+        """
+        return self._tokenizer.padding
+
+    def enable_truncation(self, max_length: int, stride: Optional[int] = 0, strategy: Optional[str] = "longest_first"):
+        """Change the truncation options
+
+        Args:
+            max_length: unsigned int:
+                The maximum length at which to truncate
+
+            stride: (`optional`) unsigned int:
+                The length of the previous first sequence to be included
+                in the overflowing sequence
+
+            strategy: (`optional`) str:
+                Can be one of `longest_first`, `only_first` or `only_second`
+        """
+        return self._tokenizer.enable_truncation(max_length, stride=stride, strategy=strategy)
+
+    def no_truncation(self):
+        """Disable truncation"""
+        return self._tokenizer.no_truncation()
+
+    @property
+    def truncation(self) -> Optional[dict]:
+        """Get the current truncation parameters
+
+        Returns:
+            None if truncation is disabled, a dict with the current truncation parameters if
+            truncation is enabled
+        """
+        return self._tokenizer.truncation
+
+    def add_tokens(self, tokens: List[Union[str, AddedToken]]) -> int:
+        """Add the given tokens to the vocabulary
+
+        Args:
+            tokens: List[Union[str, AddedToken]]:
+                A list of tokens to add to the vocabulary. Each token can either be
+                a string, or an instance of AddedToken
+
+        Returns:
+            The number of tokens that were added to the vocabulary
+        """
+        return self._tokenizer.add_tokens(tokens)
+
+    def add_special_tokens(self, special_tokens: List[Union[str, AddedToken]]) -> int:
+        """Add the given special tokens to the vocabulary, and treat them as special tokens.
+
+        The special tokens will never be processed by the model, and will be
+        removed while decoding.
+
+        Args:
+            tokens: List[Union[str, AddedToken]]:
+                A list of special tokens to add to the vocabulary. Each token can either be
+                a string, or an instance of AddedToken
+
+        Returns:
+            The number of tokens that were added to the vocabulary
+        """
+        return self._tokenizer.add_special_tokens(special_tokens)
+
+    def normalize(self, sequence: str) -> str:
+        """Normalize the given sequence
+
+        Args:
+            sequence: str:
+                The sequence to normalize
+
+        Returns:
+            The normalized string
+        """
+        return self._tokenizer.normalizer.normalize_str(sequence)
+
+    def encode(
+        self,
+        sequence: InputSequence,
+        pair: Optional[InputSequence] = None,
+        is_pretokenized: bool = False,
+        add_special_tokens: bool = True,
+    ) -> Encoding:
+        """Encode the given sequence and pair. This method can process raw text sequences as well
+        as already pre-tokenized sequences.
+
+        Args:
+            sequence: InputSequence:
+                The sequence we want to encode. This sequence can be either raw text or
+                pre-tokenized, according to the `is_pretokenized` argument:
+
+                - If `is_pretokenized=False`: `InputSequence` is expected to be `str`
+                - If `is_pretokenized=True`: `InputSequence` is expected to be
+                    `Union[List[str], Tuple[str]]`
+
+            is_pretokenized: bool:
+                Whether the input is already pre-tokenized.
+
+            add_special_tokens: bool:
+                Whether to add the special tokens while encoding.
+
+        Returns:
+            An Encoding
+        """
+        if sequence is None:
+            raise ValueError("encode: `sequence` can't be `None`")
+
+        return self._tokenizer.encode(sequence, pair, is_pretokenized, add_special_tokens)
+
+    def encode_batch(
+        self,
+        inputs: List[EncodeInput],
+        is_pretokenized: bool = False,
+        add_special_tokens: bool = True,
+    ) -> List[Encoding]:
+        """Encode the given inputs. This method accept both raw text sequences as well as already
+        pre-tokenized sequences.
+
+        Args:
+            inputs: List[EncodeInput]:
+                A list of single sequences or pair sequences to encode. Each `EncodeInput` is
+                expected to be of the following form:
+                    `Union[InputSequence, Tuple[InputSequence, InputSequence]]`
+
+                Each `InputSequence` can either be raw text or pre-tokenized,
+                according to the `is_pretokenized` argument:
+
+                - If `is_pretokenized=False`: `InputSequence` is expected to be `str`
+                - If `is_pretokenized=True`: `InputSequence` is expected to be
+                    `Union[List[str], Tuple[str]]`
+
+            is_pretokenized: bool:
+                Whether the input is already pre-tokenized.
+
+            add_special_tokens: bool:
+                Whether to add the special tokens while encoding.
+
+        Returns:
+            A list of Encoding
+        """
+
+        if inputs is None:
+            raise ValueError("encode_batch: `inputs` can't be `None`")
+
+        return self._tokenizer.encode_batch(inputs, is_pretokenized, add_special_tokens)
+
+    async def async_encode_batch(
+        self,
+        inputs: List[EncodeInput],
+        is_pretokenized: bool = False,
+        add_special_tokens: bool = True,
+    ) -> List[Encoding]:
+        """Asynchronously encode a batch (tracks character offsets).
+
+        Args:
+            inputs: A list of single or pair sequences to encode.
+            is_pretokenized: Whether inputs are already pre-tokenized.
+            add_special_tokens: Whether to add special tokens.
+
+        Returns:
+            A list of Encoding.
+        """
+        if inputs is None:
+            raise ValueError("async_encode_batch: `inputs` can't be `None`")
+        # Exposed by the Rust bindings via pyo3_async_runtimes::tokio::future_into_py
+        return await self._tokenizer.async_encode_batch(inputs, is_pretokenized, add_special_tokens)
+
+    async def async_encode_batch_fast(
+        self,
+        inputs: List[EncodeInput],
+        is_pretokenized: bool = False,
+        add_special_tokens: bool = True,
+    ) -> List[Encoding]:
+        """Asynchronously encode a batch (no character offsets, faster).
+
+        Args:
+            inputs: A list of single or pair sequences to encode.
+            is_pretokenized: Whether inputs are already pre-tokenized.
+            add_special_tokens: Whether to add special tokens.
+
+        Returns:
+            A list of Encoding.
+        """
+        if inputs is None:
+            raise ValueError("async_encode_batch_fast: `inputs` can't be `None`")
+        return await self._tokenizer.async_encode_batch_fast(inputs, is_pretokenized, add_special_tokens)
+
+    def decode(self, ids: List[int], skip_special_tokens: Optional[bool] = True) -> str:
+        """Decode the given list of ids to a string sequence
+
+        Args:
+            ids: List[unsigned int]:
+                A list of ids to be decoded
+
+            skip_special_tokens: (`optional`) boolean:
+                Whether to remove all the special tokens from the output string
+
+        Returns:
+            The decoded string
+        """
+        if ids is None:
+            raise ValueError("None input is not valid. Should be a list of integers.")
+
+        return self._tokenizer.decode(ids, skip_special_tokens=skip_special_tokens)
+
+    def decode_batch(self, sequences: List[List[int]], skip_special_tokens: Optional[bool] = True) -> str:
+        """Decode the list of sequences to a list of string sequences
+
+        Args:
+            sequences: List[List[unsigned int]]:
+                A list of sequence of ids to be decoded
+
+            skip_special_tokens: (`optional`) boolean:
+                Whether to remove all the special tokens from the output strings
+
+        Returns:
+            A list of decoded strings
+        """
+        if sequences is None:
+            raise ValueError("None input is not valid. Should be list of list of integers.")
+
+        return self._tokenizer.decode_batch(sequences, skip_special_tokens=skip_special_tokens)
+
+    def token_to_id(self, token: str) -> Optional[int]:
+        """Convert the given token to its corresponding id
+
+        Args:
+            token: str:
+                The token to convert
+
+        Returns:
+            The corresponding id if it exists, None otherwise
+        """
+        return self._tokenizer.token_to_id(token)
+
+    def id_to_token(self, id: int) -> Optional[str]:
+        """Convert the given token id to its corresponding string
+
+        Args:
+            token: id:
+                The token id to convert
+
+        Returns:
+            The corresponding string if it exists, None otherwise
+        """
+        return self._tokenizer.id_to_token(id)
+
+    def save_model(self, directory: str, prefix: Optional[str] = None):
+        """Save the current model to the given directory
+
+        Args:
+            directory: str:
+                A path to the destination directory
+
+            prefix: (Optional) str:
+                An optional prefix, used to prefix each file name
+        """
+        return self._tokenizer.model.save(directory, prefix=prefix)
+
+    def save(self, path: str, pretty: bool = True):
+        """Save the current Tokenizer at the given path
+
+        Args:
+            path: str:
+                A path to the destination Tokenizer file
+        """
+        return self._tokenizer.save(path, pretty)
+
+    def to_str(self, pretty: bool = False):
+        """Get a serialized JSON version of the Tokenizer as a str
+
+        Args:
+            pretty: bool:
+                Whether the JSON string should be prettified
+
+        Returns:
+            str
+        """
+        return self._tokenizer.to_str(pretty)
+
+    def post_process(
+        self, encoding: Encoding, pair: Optional[Encoding] = None, add_special_tokens: bool = True
+    ) -> Encoding:
+        """Apply all the post-processing steps to the given encodings.
+
+        The various steps are:
+            1. Truncate according to global params (provided to `enable_truncation`)
+            2. Apply the PostProcessor
+            3. Pad according to global params. (provided to `enable_padding`)
+
+        Args:
+            encoding: Encoding:
+                The main Encoding to post process
+
+            pair: Optional[Encoding]:
+                An optional pair Encoding
+
+            add_special_tokens: bool:
+                Whether to add special tokens
+
+        Returns:
+            The resulting Encoding
+        """
+        return self._tokenizer.post_process(encoding, pair, add_special_tokens)
+
+    @property
+    def model(self) -> Model:
+        return self._tokenizer.model
+
+    @model.setter
+    def model(self, model: Model):
+        self._tokenizer.model = model
+
+    @property
+    def normalizer(self) -> Normalizer:
+        return self._tokenizer.normalizer
+
+    @normalizer.setter
+    def normalizer(self, normalizer: Normalizer):
+        self._tokenizer.normalizer = normalizer
+
+    @property
+    def pre_tokenizer(self) -> PreTokenizer:
+        return self._tokenizer.pre_tokenizer
+
+    @pre_tokenizer.setter
+    def pre_tokenizer(self, pre_tokenizer: PreTokenizer):
+        self._tokenizer.pre_tokenizer = pre_tokenizer
+
+    @property
+    def post_processor(self) -> PostProcessor:
+        return self._tokenizer.post_processor
+
+    @post_processor.setter
+    def post_processor(self, post_processor: PostProcessor):
+        self._tokenizer.post_processor = post_processor
+
+    @property
+    def decoder(self) -> Decoder:
+        return self._tokenizer.decoder
+
+    @decoder.setter
+    def decoder(self, decoder: Decoder):
+        self._tokenizer.decoder = decoder
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/bert_wordpiece.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/bert_wordpiece.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f34e3ca8a4f8b3ed454e09d828918881232ef90
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/bert_wordpiece.py
@@ -0,0 +1,151 @@
+from typing import Dict, Iterator, List, Optional, Union
+
+from tokenizers import AddedToken, Tokenizer, decoders, trainers
+from tokenizers.models import WordPiece
+from tokenizers.normalizers import BertNormalizer
+from tokenizers.pre_tokenizers import BertPreTokenizer
+from tokenizers.processors import BertProcessing
+
+from .base_tokenizer import BaseTokenizer
+
+
+class BertWordPieceTokenizer(BaseTokenizer):
+    """Bert WordPiece Tokenizer"""
+
+    def __init__(
+        self,
+        vocab: Optional[Union[str, Dict[str, int]]] = None,
+        unk_token: Union[str, AddedToken] = "[UNK]",
+        sep_token: Union[str, AddedToken] = "[SEP]",
+        cls_token: Union[str, AddedToken] = "[CLS]",
+        pad_token: Union[str, AddedToken] = "[PAD]",
+        mask_token: Union[str, AddedToken] = "[MASK]",
+        clean_text: bool = True,
+        handle_chinese_chars: bool = True,
+        strip_accents: Optional[bool] = None,
+        lowercase: bool = True,
+        wordpieces_prefix: str = "##",
+    ):
+        if vocab is not None:
+            tokenizer = Tokenizer(WordPiece(vocab, unk_token=str(unk_token)))
+        else:
+            tokenizer = Tokenizer(WordPiece(unk_token=str(unk_token)))
+
+        # Let the tokenizer know about special tokens if they are part of the vocab
+        if tokenizer.token_to_id(str(unk_token)) is not None:
+            tokenizer.add_special_tokens([str(unk_token)])
+        if tokenizer.token_to_id(str(sep_token)) is not None:
+            tokenizer.add_special_tokens([str(sep_token)])
+        if tokenizer.token_to_id(str(cls_token)) is not None:
+            tokenizer.add_special_tokens([str(cls_token)])
+        if tokenizer.token_to_id(str(pad_token)) is not None:
+            tokenizer.add_special_tokens([str(pad_token)])
+        if tokenizer.token_to_id(str(mask_token)) is not None:
+            tokenizer.add_special_tokens([str(mask_token)])
+
+        tokenizer.normalizer = BertNormalizer(
+            clean_text=clean_text,
+            handle_chinese_chars=handle_chinese_chars,
+            strip_accents=strip_accents,
+            lowercase=lowercase,
+        )
+        tokenizer.pre_tokenizer = BertPreTokenizer()
+
+        if vocab is not None:
+            sep_token_id = tokenizer.token_to_id(str(sep_token))
+            if sep_token_id is None:
+                raise TypeError("sep_token not found in the vocabulary")
+            cls_token_id = tokenizer.token_to_id(str(cls_token))
+            if cls_token_id is None:
+                raise TypeError("cls_token not found in the vocabulary")
+
+            tokenizer.post_processor = BertProcessing((str(sep_token), sep_token_id), (str(cls_token), cls_token_id))
+        tokenizer.decoder = decoders.WordPiece(prefix=wordpieces_prefix)
+
+        parameters = {
+            "model": "BertWordPiece",
+            "unk_token": unk_token,
+            "sep_token": sep_token,
+            "cls_token": cls_token,
+            "pad_token": pad_token,
+            "mask_token": mask_token,
+            "clean_text": clean_text,
+            "handle_chinese_chars": handle_chinese_chars,
+            "strip_accents": strip_accents,
+            "lowercase": lowercase,
+            "wordpieces_prefix": wordpieces_prefix,
+        }
+
+        super().__init__(tokenizer, parameters)
+
+    @staticmethod
+    def from_file(vocab: str, **kwargs):
+        vocab = WordPiece.read_file(vocab)
+        return BertWordPieceTokenizer(vocab, **kwargs)
+
+    def train(
+        self,
+        files: Union[str, List[str]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        special_tokens: List[Union[str, AddedToken]] = [
+            "[PAD]",
+            "[UNK]",
+            "[CLS]",
+            "[SEP]",
+            "[MASK]",
+        ],
+        show_progress: bool = True,
+        wordpieces_prefix: str = "##",
+    ):
+        """Train the model using the given files"""
+
+        trainer = trainers.WordPieceTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            special_tokens=special_tokens,
+            show_progress=show_progress,
+            continuing_subword_prefix=wordpieces_prefix,
+        )
+        if isinstance(files, str):
+            files = [files]
+        self._tokenizer.train(files, trainer=trainer)
+
+    def train_from_iterator(
+        self,
+        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        special_tokens: List[Union[str, AddedToken]] = [
+            "[PAD]",
+            "[UNK]",
+            "[CLS]",
+            "[SEP]",
+            "[MASK]",
+        ],
+        show_progress: bool = True,
+        wordpieces_prefix: str = "##",
+        length: Optional[int] = None,
+    ):
+        """Train the model using the given iterator"""
+
+        trainer = trainers.WordPieceTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            special_tokens=special_tokens,
+            show_progress=show_progress,
+            continuing_subword_prefix=wordpieces_prefix,
+        )
+        self._tokenizer.train_from_iterator(
+            iterator,
+            trainer=trainer,
+            length=length,
+        )
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/byte_level_bpe.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/byte_level_bpe.py
new file mode 100644
index 0000000000000000000000000000000000000000..f65f05e1ddd4c8ec6b3791aa3045762cc06523e3
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/byte_level_bpe.py
@@ -0,0 +1,122 @@
+from typing import Dict, Iterator, List, Optional, Tuple, Union
+
+from tokenizers import AddedToken, Tokenizer, decoders, pre_tokenizers, processors, trainers
+from tokenizers.models import BPE
+from tokenizers.normalizers import Lowercase, Sequence, unicode_normalizer_from_str
+
+from .base_tokenizer import BaseTokenizer
+
+
+class ByteLevelBPETokenizer(BaseTokenizer):
+    """ByteLevelBPETokenizer
+
+    Represents a Byte-level BPE as introduced by OpenAI with their GPT-2 model
+    """
+
+    def __init__(
+        self,
+        vocab: Optional[Union[str, Dict[str, int]]] = None,
+        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
+        add_prefix_space: bool = False,
+        lowercase: bool = False,
+        dropout: Optional[float] = None,
+        unicode_normalizer: Optional[str] = None,
+        continuing_subword_prefix: Optional[str] = None,
+        end_of_word_suffix: Optional[str] = None,
+        trim_offsets: bool = False,
+    ):
+        if vocab is not None and merges is not None:
+            tokenizer = Tokenizer(
+                BPE(
+                    vocab,
+                    merges,
+                    dropout=dropout,
+                    continuing_subword_prefix=continuing_subword_prefix or "",
+                    end_of_word_suffix=end_of_word_suffix or "",
+                )
+            )
+        else:
+            tokenizer = Tokenizer(BPE())
+
+        # Check for Unicode normalization first (before everything else)
+        normalizers = []
+
+        if unicode_normalizer:
+            normalizers += [unicode_normalizer_from_str(unicode_normalizer)]
+
+        if lowercase:
+            normalizers += [Lowercase()]
+
+        # Create the normalizer structure
+        if len(normalizers) > 0:
+            if len(normalizers) > 1:
+                tokenizer.normalizer = Sequence(normalizers)
+            else:
+                tokenizer.normalizer = normalizers[0]
+
+        tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=add_prefix_space)
+        tokenizer.decoder = decoders.ByteLevel()
+        tokenizer.post_processor = processors.ByteLevel(trim_offsets=trim_offsets)
+
+        parameters = {
+            "model": "ByteLevelBPE",
+            "add_prefix_space": add_prefix_space,
+            "lowercase": lowercase,
+            "dropout": dropout,
+            "unicode_normalizer": unicode_normalizer,
+            "continuing_subword_prefix": continuing_subword_prefix,
+            "end_of_word_suffix": end_of_word_suffix,
+            "trim_offsets": trim_offsets,
+        }
+
+        super().__init__(tokenizer, parameters)
+
+    @staticmethod
+    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
+        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
+        return ByteLevelBPETokenizer(vocab, merges, **kwargs)
+
+    def train(
+        self,
+        files: Union[str, List[str]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        show_progress: bool = True,
+        special_tokens: List[Union[str, AddedToken]] = [],
+    ):
+        """Train the model using the given files"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            show_progress=show_progress,
+            special_tokens=special_tokens,
+            initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
+        )
+        if isinstance(files, str):
+            files = [files]
+        self._tokenizer.train(files, trainer=trainer)
+
+    def train_from_iterator(
+        self,
+        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        show_progress: bool = True,
+        special_tokens: List[Union[str, AddedToken]] = [],
+        length: Optional[int] = None,
+    ):
+        """Train the model using the given iterator"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            show_progress=show_progress,
+            special_tokens=special_tokens,
+            initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
+        )
+        self._tokenizer.train_from_iterator(
+            iterator,
+            trainer=trainer,
+            length=length,
+        )
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/char_level_bpe.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/char_level_bpe.py
new file mode 100644
index 0000000000000000000000000000000000000000..62b5bcdf06b4026ce48620ee4d681f0c7399b520
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/char_level_bpe.py
@@ -0,0 +1,150 @@
+from typing import Dict, Iterator, List, Optional, Tuple, Union
+
+from .. import AddedToken, Tokenizer, decoders, pre_tokenizers, trainers
+from ..models import BPE
+from ..normalizers import BertNormalizer, Lowercase, Sequence, unicode_normalizer_from_str
+from .base_tokenizer import BaseTokenizer
+
+
+class CharBPETokenizer(BaseTokenizer):
+    """Original BPE Tokenizer
+
+    Represents the BPE algorithm, as introduced by Rico Sennrich
+    (https://arxiv.org/abs/1508.07909)
+
+    The defaults settings corresponds to OpenAI GPT BPE tokenizers and differs from the original
+    Sennrich subword-nmt implementation by the following options that you can deactivate:
+        - adding a normalizer to clean up the text (deactivate with `bert_normalizer=False`) by:
+            * removing any control characters and replacing all whitespaces by the classic one.
+            * handle chinese chars by putting spaces around them.
+            * strip all accents.
+        - spitting on punctuation in addition to whitespaces (deactivate it with
+          `split_on_whitespace_only=True`)
+    """
+
+    def __init__(
+        self,
+        vocab: Optional[Union[str, Dict[str, int]]] = None,
+        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
+        unk_token: Union[str, AddedToken] = "",
+        suffix: str = "",
+        dropout: Optional[float] = None,
+        lowercase: bool = False,
+        unicode_normalizer: Optional[str] = None,
+        bert_normalizer: bool = True,
+        split_on_whitespace_only: bool = False,
+    ):
+        if vocab is not None and merges is not None:
+            tokenizer = Tokenizer(
+                BPE(
+                    vocab,
+                    merges,
+                    dropout=dropout,
+                    unk_token=str(unk_token),
+                    end_of_word_suffix=suffix,
+                )
+            )
+        else:
+            tokenizer = Tokenizer(BPE(unk_token=str(unk_token), dropout=dropout, end_of_word_suffix=suffix))
+
+        if tokenizer.token_to_id(str(unk_token)) is not None:
+            tokenizer.add_special_tokens([str(unk_token)])
+
+        # Check for Unicode normalization first (before everything else)
+        normalizers = []
+
+        if unicode_normalizer:
+            normalizers += [unicode_normalizer_from_str(unicode_normalizer)]
+
+        if bert_normalizer:
+            normalizers += [BertNormalizer(lowercase=False)]
+
+        if lowercase:
+            normalizers += [Lowercase()]
+
+        # Create the normalizer structure
+        if len(normalizers) > 0:
+            if len(normalizers) > 1:
+                tokenizer.normalizer = Sequence(normalizers)
+            else:
+                tokenizer.normalizer = normalizers[0]
+
+        if split_on_whitespace_only:
+            tokenizer.pre_tokenizer = pre_tokenizers.WhitespaceSplit()
+        else:
+            tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()
+
+        tokenizer.decoder = decoders.BPEDecoder(suffix=suffix)
+
+        parameters = {
+            "model": "BPE",
+            "unk_token": unk_token,
+            "suffix": suffix,
+            "dropout": dropout,
+            "lowercase": lowercase,
+            "unicode_normalizer": unicode_normalizer,
+            "bert_normalizer": bert_normalizer,
+            "split_on_whitespace_only": split_on_whitespace_only,
+        }
+
+        super().__init__(tokenizer, parameters)
+
+    @staticmethod
+    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
+        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
+        return CharBPETokenizer(vocab, merges, **kwargs)
+
+    def train(
+        self,
+        files: Union[str, List[str]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        special_tokens: List[Union[str, AddedToken]] = [""],
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        suffix: Optional[str] = "",
+        show_progress: bool = True,
+    ):
+        """Train the model using the given files"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            special_tokens=special_tokens,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            end_of_word_suffix=suffix,
+            show_progress=show_progress,
+        )
+        if isinstance(files, str):
+            files = [files]
+        self._tokenizer.train(files, trainer=trainer)
+
+    def train_from_iterator(
+        self,
+        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        special_tokens: List[Union[str, AddedToken]] = [""],
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        suffix: Optional[str] = "",
+        show_progress: bool = True,
+        length: Optional[int] = None,
+    ):
+        """Train the model using the given iterator"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            special_tokens=special_tokens,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            end_of_word_suffix=suffix,
+            show_progress=show_progress,
+        )
+        self._tokenizer.train_from_iterator(
+            iterator,
+            trainer=trainer,
+            length=length,
+        )
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_bpe.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_bpe.py
new file mode 100644
index 0000000000000000000000000000000000000000..26200489a60dfc6420b43f5dda21ad18ebfe7484
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_bpe.py
@@ -0,0 +1,103 @@
+from typing import Dict, Iterator, List, Optional, Tuple, Union
+
+from tokenizers import AddedToken, Tokenizer, decoders, pre_tokenizers, trainers
+from tokenizers.models import BPE
+from tokenizers.normalizers import NFKC
+
+from .base_tokenizer import BaseTokenizer
+
+
+class SentencePieceBPETokenizer(BaseTokenizer):
+    """SentencePiece BPE Tokenizer
+
+    Represents the BPE algorithm, with the pretokenization used by SentencePiece
+    """
+
+    def __init__(
+        self,
+        vocab: Optional[Union[str, Dict[str, int]]] = None,
+        merges: Optional[Union[str, List[Tuple[str, str]]]] = None,
+        unk_token: Union[str, AddedToken] = "",
+        replacement: str = "▁",
+        add_prefix_space: bool = True,
+        dropout: Optional[float] = None,
+        fuse_unk: Optional[bool] = False,
+    ):
+        if vocab is not None and merges is not None:
+            tokenizer = Tokenizer(BPE(vocab, merges, dropout=dropout, unk_token=unk_token, fuse_unk=fuse_unk))
+        else:
+            tokenizer = Tokenizer(BPE(dropout=dropout, unk_token=unk_token, fuse_unk=fuse_unk))
+
+        if tokenizer.token_to_id(str(unk_token)) is not None:
+            tokenizer.add_special_tokens([str(unk_token)])
+
+        tokenizer.normalizer = NFKC()
+        prepend_scheme = "always" if add_prefix_space else "never"
+        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+
+        parameters = {
+            "model": "SentencePieceBPE",
+            "unk_token": unk_token,
+            "replacement": replacement,
+            "add_prefix_space": add_prefix_space,
+            "dropout": dropout,
+        }
+
+        super().__init__(tokenizer, parameters)
+
+    @staticmethod
+    def from_file(vocab_filename: str, merges_filename: str, **kwargs):
+        vocab, merges = BPE.read_file(vocab_filename, merges_filename)
+        return SentencePieceBPETokenizer(vocab, merges, **kwargs)
+
+    def train(
+        self,
+        files: Union[str, List[str]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        special_tokens: List[Union[str, AddedToken]] = [""],
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        show_progress: bool = True,
+    ):
+        """Train the model using the given files"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            special_tokens=special_tokens,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            show_progress=show_progress,
+        )
+        if isinstance(files, str):
+            files = [files]
+        self._tokenizer.train(files, trainer=trainer)
+
+    def train_from_iterator(
+        self,
+        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
+        vocab_size: int = 30000,
+        min_frequency: int = 2,
+        special_tokens: List[Union[str, AddedToken]] = [""],
+        limit_alphabet: int = 1000,
+        initial_alphabet: List[str] = [],
+        show_progress: bool = True,
+        length: Optional[int] = None,
+    ):
+        """Train the model using the given iterator"""
+
+        trainer = trainers.BpeTrainer(
+            vocab_size=vocab_size,
+            min_frequency=min_frequency,
+            special_tokens=special_tokens,
+            limit_alphabet=limit_alphabet,
+            initial_alphabet=initial_alphabet,
+            show_progress=show_progress,
+        )
+        self._tokenizer.train_from_iterator(
+            iterator,
+            trainer=trainer,
+            length=length,
+        )
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_unigram.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_unigram.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e945a433686be6643363a140b17dd56e64013f9
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/implementations/sentencepiece_unigram.py
@@ -0,0 +1,196 @@
+import json
+import os
+from typing import Iterator, List, Optional, Union, Tuple
+
+from tokenizers import AddedToken, Regex, Tokenizer, decoders, normalizers, pre_tokenizers, trainers
+from tokenizers.models import Unigram
+
+from .base_tokenizer import BaseTokenizer
+
+
+class SentencePieceUnigramTokenizer(BaseTokenizer):
+    """SentencePiece Unigram Tokenizer
+
+    Represents the Unigram algorithm, with the pretokenization used by SentencePiece
+    """
+
+    def __init__(
+        self,
+        vocab: Optional[List[Tuple[str, float]]] = None,
+        replacement: str = "▁",
+        add_prefix_space: bool = True,
+    ):
+        if vocab is not None:
+            # Let Unigram(..) fail if only one of them is None
+            tokenizer = Tokenizer(Unigram(vocab))
+        else:
+            tokenizer = Tokenizer(Unigram())
+
+        tokenizer.normalizer = normalizers.Sequence(
+            [normalizers.Nmt(), normalizers.NFKC(), normalizers.Replace(Regex(" {2,}"), " ")]
+        )
+        prepend_scheme = "always" if add_prefix_space else "never"
+        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+
+        parameters = {
+            "model": "SentencePieceUnigram",
+            "replacement": replacement,
+            "add_prefix_space": add_prefix_space,
+        }
+
+        super().__init__(tokenizer, parameters)
+
+    def train(
+        self,
+        files: Union[str, List[str]],
+        vocab_size: int = 8000,
+        show_progress: bool = True,
+        special_tokens: Optional[List[Union[str, AddedToken]]] = None,
+        initial_alphabet: Optional[List[str]] = None,
+        unk_token: Optional[str] = None,
+    ):
+        """
+        Train the model using the given files
+
+        Args:
+            files (:obj:`List[str]`):
+                A list of path to the files that we should use for training
+            vocab_size (:obj:`int`):
+                The size of the final vocabulary, including all tokens and alphabet.
+            show_progress (:obj:`bool`):
+                Whether to show progress bars while training.
+            special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
+                A list of special tokens the model should know of.
+            initial_alphabet (:obj:`List[str]`, `optional`):
+                A list of characters to include in the initial alphabet, even
+                if not seen in the training dataset.
+                If the strings contain more than one character, only the first one
+                is kept.
+            unk_token (:obj:`str`, `optional`):
+                The unknown token to be used by the model.
+        """
+
+        if special_tokens is None:
+            special_tokens = []
+
+        if initial_alphabet is None:
+            initial_alphabet = []
+
+        trainer = trainers.UnigramTrainer(
+            vocab_size=vocab_size,
+            special_tokens=special_tokens,
+            show_progress=show_progress,
+            initial_alphabet=initial_alphabet,
+            unk_token=unk_token,
+        )
+
+        if isinstance(files, str):
+            files = [files]
+        self._tokenizer.train(files, trainer=trainer)
+
+    def train_from_iterator(
+        self,
+        iterator: Union[Iterator[str], Iterator[Iterator[str]]],
+        vocab_size: int = 8000,
+        show_progress: bool = True,
+        special_tokens: Optional[List[Union[str, AddedToken]]] = None,
+        initial_alphabet: Optional[List[str]] = None,
+        unk_token: Optional[str] = None,
+        length: Optional[int] = None,
+    ):
+        """
+        Train the model using the given iterator
+
+        Args:
+            iterator (:obj:`Union[Iterator[str], Iterator[Iterator[str]]]`):
+                Any iterator over strings or list of strings
+            vocab_size (:obj:`int`):
+                The size of the final vocabulary, including all tokens and alphabet.
+            show_progress (:obj:`bool`):
+                Whether to show progress bars while training.
+            special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
+                A list of special tokens the model should know of.
+            initial_alphabet (:obj:`List[str]`, `optional`):
+                A list of characters to include in the initial alphabet, even
+                if not seen in the training dataset.
+                If the strings contain more than one character, only the first one
+                is kept.
+            unk_token (:obj:`str`, `optional`):
+                The unknown token to be used by the model.
+            length (:obj:`int`, `optional`):
+                The total number of sequences in the iterator. This is used to
+                provide meaningful progress tracking
+        """
+
+        if special_tokens is None:
+            special_tokens = []
+
+        if initial_alphabet is None:
+            initial_alphabet = []
+
+        trainer = trainers.UnigramTrainer(
+            vocab_size=vocab_size,
+            special_tokens=special_tokens,
+            show_progress=show_progress,
+            initial_alphabet=initial_alphabet,
+            unk_token=unk_token,
+        )
+
+        self._tokenizer.train_from_iterator(
+            iterator,
+            trainer=trainer,
+            length=length,
+        )
+
+    @staticmethod
+    def from_spm(filename: str):
+        try:
+            import sys
+
+            sys.path.append(".")
+
+            import sentencepiece_model_pb2 as model  # type: ignore[import]
+        except Exception:
+            raise Exception(
+                "You don't seem to have the required protobuf file, in order to use this function you need to run `pip install protobuf` and `wget https://raw.githubusercontent.com/google/sentencepiece/master/python/src/sentencepiece/sentencepiece_model_pb2.py` for us to be able to read the intrinsics of your spm_file. `pip install sentencepiece` is not required."
+            )
+
+        m = model.ModelProto()
+        m.ParseFromString(open(filename, "rb").read())
+
+        precompiled_charsmap = m.normalizer_spec.precompiled_charsmap
+        vocab = [(piece.piece, piece.score) for piece in m.pieces]
+        unk_id = m.trainer_spec.unk_id
+        model_type = m.trainer_spec.model_type
+        byte_fallback = m.trainer_spec.byte_fallback
+        if model_type != 1:
+            raise Exception(
+                "You're trying to run a `Unigram` model but you're file was trained with a different algorithm"
+            )
+
+        replacement = "▁"
+        add_prefix_space = True
+
+        tokenizer = Tokenizer(Unigram(vocab, unk_id, byte_fallback))
+
+        if precompiled_charsmap:
+            tokenizer.normalizer = normalizers.Sequence(
+                [
+                    normalizers.Precompiled(precompiled_charsmap),
+                    normalizers.Replace(Regex(" {2,}"), " "),
+                ]
+            )
+        else:
+            tokenizer.normalizer = normalizers.Sequence([normalizers.Replace(Regex(" {2,}"), " ")])
+        prepend_scheme = "always" if add_prefix_space else "never"
+        tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+        tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
+
+        parameters = {
+            "model": "SentencePieceUnigram",
+        }
+
+        obj = BaseTokenizer.__new__(SentencePieceUnigramTokenizer, tokenizer, parameters)  # type: ignore[arg-type]
+        BaseTokenizer.__init__(obj, tokenizer, parameters)
+        return obj
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..68ac211aa8032249db6b929ca64f9130c358d40b
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.py
@@ -0,0 +1,8 @@
+# Generated content DO NOT EDIT
+from .. import models
+
+Model = models.Model
+BPE = models.BPE
+Unigram = models.Unigram
+WordLevel = models.WordLevel
+WordPiece = models.WordPiece
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..2548697410ffb5d0143c2d26df36fcf4fc0de242
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__init__.pyi
@@ -0,0 +1,744 @@
+# Generated content DO NOT EDIT
+class Model:
+    """
+    Base class for all models
+
+    The model represents the actual tokenization algorithm. This is the part that
+    will contain and manage the learned vocabulary.
+
+    This class cannot be constructed directly. Please use one of the concrete models.
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    def get_trainer(self):
+        """
+        Get the associated :class:`~tokenizers.trainers.Trainer`
+
+        Retrieve the :class:`~tokenizers.trainers.Trainer` associated to this
+        :class:`~tokenizers.models.Model`.
+
+        Returns:
+            :class:`~tokenizers.trainers.Trainer`: The Trainer used to train this model
+        """
+        pass
+
+    def id_to_token(self, id):
+        """
+        Get the token associated to an ID
+
+        Args:
+            id (:obj:`int`):
+                An ID to convert to a token
+
+        Returns:
+            :obj:`str`: The token associated to the ID
+        """
+        pass
+
+    def save(self, folder, prefix):
+        """
+        Save the current model
+
+        Save the current model in the given folder, using the given prefix for the various
+        files that will get created.
+        Any file with the same name that already exists in this folder will be overwritten.
+
+        Args:
+            folder (:obj:`str`):
+                The path to the target folder in which to save the various files
+
+            prefix (:obj:`str`, `optional`):
+                An optional prefix, used to prefix each file name
+
+        Returns:
+            :obj:`List[str]`: The list of saved files
+        """
+        pass
+
+    def token_to_id(self, tokens):
+        """
+        Get the ID associated to a token
+
+        Args:
+            token (:obj:`str`):
+                A token to convert to an ID
+
+        Returns:
+            :obj:`int`: The ID associated to the token
+        """
+        pass
+
+    def tokenize(self, sequence):
+        """
+        Tokenize a sequence
+
+        Args:
+            sequence (:obj:`str`):
+                A sequence to tokenize
+
+        Returns:
+            A :obj:`List` of :class:`~tokenizers.Token`: The generated tokens
+        """
+        pass
+
+class BPE(Model):
+    """
+    An implementation of the BPE (Byte-Pair Encoding) algorithm
+
+    Args:
+        vocab (:obj:`Dict[str, int]`, `optional`):
+            A dictionary of string keys and their ids :obj:`{"am": 0,...}`
+
+        merges (:obj:`List[Tuple[str, str]]`, `optional`):
+            A list of pairs of tokens (:obj:`Tuple[str, str]`) :obj:`[("a", "b"),...]`
+
+        cache_capacity (:obj:`int`, `optional`):
+            The number of words that the BPE cache can contain. The cache allows
+            to speed-up the process by keeping the result of the merge operations
+            for a number of words.
+
+        dropout (:obj:`float`, `optional`):
+            A float between 0 and 1 that represents the BPE dropout to use.
+
+        unk_token (:obj:`str`, `optional`):
+            The unknown token to be used by the model.
+
+        continuing_subword_prefix (:obj:`str`, `optional`):
+            The prefix to attach to subword units that don't represent a beginning of word.
+
+        end_of_word_suffix (:obj:`str`, `optional`):
+            The suffix to attach to subword units that represent an end of word.
+
+        fuse_unk (:obj:`bool`, `optional`):
+            Whether to fuse any subsequent unknown tokens into a single one
+
+        byte_fallback (:obj:`bool`, `optional`):
+            Whether to use spm byte-fallback trick (defaults to False)
+
+        ignore_merges (:obj:`bool`, `optional`):
+            Whether or not to match tokens with the vocab before using merges.
+    """
+    def __init__(
+        self,
+        vocab=None,
+        merges=None,
+        cache_capacity=None,
+        dropout=None,
+        unk_token=None,
+        continuing_subword_prefix=None,
+        end_of_word_suffix=None,
+        fuse_unk=None,
+        byte_fallback=False,
+        ignore_merges=False,
+    ):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def byte_fallback(self):
+        """ """
+        pass
+
+    @byte_fallback.setter
+    def byte_fallback(self, value):
+        """ """
+        pass
+
+    @property
+    def continuing_subword_prefix(self):
+        """ """
+        pass
+
+    @continuing_subword_prefix.setter
+    def continuing_subword_prefix(self, value):
+        """ """
+        pass
+
+    @property
+    def dropout(self):
+        """ """
+        pass
+
+    @dropout.setter
+    def dropout(self, value):
+        """ """
+        pass
+
+    @property
+    def end_of_word_suffix(self):
+        """ """
+        pass
+
+    @end_of_word_suffix.setter
+    def end_of_word_suffix(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def from_file(vocab, merges, **kwargs):
+        """
+        Instantiate a BPE model from the given files.
+
+        This method is roughly equivalent to doing::
+
+           vocab, merges = BPE.read_file(vocab_filename, merges_filename)
+           bpe = BPE(vocab, merges)
+
+        If you don't need to keep the :obj:`vocab, merges` values lying around,
+        this method is more optimized than manually calling
+        :meth:`~tokenizers.models.BPE.read_file` to initialize a :class:`~tokenizers.models.BPE`
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.json` file
+
+            merges (:obj:`str`):
+                The path to a :obj:`merges.txt` file
+
+        Returns:
+            :class:`~tokenizers.models.BPE`: An instance of BPE loaded from these files
+        """
+        pass
+
+    @property
+    def fuse_unk(self):
+        """ """
+        pass
+
+    @fuse_unk.setter
+    def fuse_unk(self, value):
+        """ """
+        pass
+
+    def get_trainer(self):
+        """
+        Get the associated :class:`~tokenizers.trainers.Trainer`
+
+        Retrieve the :class:`~tokenizers.trainers.Trainer` associated to this
+        :class:`~tokenizers.models.Model`.
+
+        Returns:
+            :class:`~tokenizers.trainers.Trainer`: The Trainer used to train this model
+        """
+        pass
+
+    def id_to_token(self, id):
+        """
+        Get the token associated to an ID
+
+        Args:
+            id (:obj:`int`):
+                An ID to convert to a token
+
+        Returns:
+            :obj:`str`: The token associated to the ID
+        """
+        pass
+
+    @property
+    def ignore_merges(self):
+        """ """
+        pass
+
+    @ignore_merges.setter
+    def ignore_merges(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def read_file(vocab, merges):
+        """
+        Read a :obj:`vocab.json` and a :obj:`merges.txt` files
+
+        This method provides a way to read and parse the content of these files,
+        returning the relevant data structures. If you want to instantiate some BPE models
+        from memory, this method gives you the expected input from the standard files.
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.json` file
+
+            merges (:obj:`str`):
+                The path to a :obj:`merges.txt` file
+
+        Returns:
+            A :obj:`Tuple` with the vocab and the merges:
+                The vocabulary and merges loaded into memory
+        """
+        pass
+
+    def save(self, folder, prefix):
+        """
+        Save the current model
+
+        Save the current model in the given folder, using the given prefix for the various
+        files that will get created.
+        Any file with the same name that already exists in this folder will be overwritten.
+
+        Args:
+            folder (:obj:`str`):
+                The path to the target folder in which to save the various files
+
+            prefix (:obj:`str`, `optional`):
+                An optional prefix, used to prefix each file name
+
+        Returns:
+            :obj:`List[str]`: The list of saved files
+        """
+        pass
+
+    def token_to_id(self, tokens):
+        """
+        Get the ID associated to a token
+
+        Args:
+            token (:obj:`str`):
+                A token to convert to an ID
+
+        Returns:
+            :obj:`int`: The ID associated to the token
+        """
+        pass
+
+    def tokenize(self, sequence):
+        """
+        Tokenize a sequence
+
+        Args:
+            sequence (:obj:`str`):
+                A sequence to tokenize
+
+        Returns:
+            A :obj:`List` of :class:`~tokenizers.Token`: The generated tokens
+        """
+        pass
+
+    @property
+    def unk_token(self):
+        """ """
+        pass
+
+    @unk_token.setter
+    def unk_token(self, value):
+        """ """
+        pass
+
+class Unigram(Model):
+    """
+    An implementation of the Unigram algorithm
+
+    Args:
+        vocab (:obj:`List[Tuple[str, float]]`, `optional`, `optional`):
+            A list of vocabulary items and their relative score [("am", -0.2442),...]
+    """
+    def __init__(self, vocab=None, unk_id=None, byte_fallback=None):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    def get_trainer(self):
+        """
+        Get the associated :class:`~tokenizers.trainers.Trainer`
+
+        Retrieve the :class:`~tokenizers.trainers.Trainer` associated to this
+        :class:`~tokenizers.models.Model`.
+
+        Returns:
+            :class:`~tokenizers.trainers.Trainer`: The Trainer used to train this model
+        """
+        pass
+
+    def id_to_token(self, id):
+        """
+        Get the token associated to an ID
+
+        Args:
+            id (:obj:`int`):
+                An ID to convert to a token
+
+        Returns:
+            :obj:`str`: The token associated to the ID
+        """
+        pass
+
+    def save(self, folder, prefix):
+        """
+        Save the current model
+
+        Save the current model in the given folder, using the given prefix for the various
+        files that will get created.
+        Any file with the same name that already exists in this folder will be overwritten.
+
+        Args:
+            folder (:obj:`str`):
+                The path to the target folder in which to save the various files
+
+            prefix (:obj:`str`, `optional`):
+                An optional prefix, used to prefix each file name
+
+        Returns:
+            :obj:`List[str]`: The list of saved files
+        """
+        pass
+
+    def token_to_id(self, tokens):
+        """
+        Get the ID associated to a token
+
+        Args:
+            token (:obj:`str`):
+                A token to convert to an ID
+
+        Returns:
+            :obj:`int`: The ID associated to the token
+        """
+        pass
+
+    def tokenize(self, sequence):
+        """
+        Tokenize a sequence
+
+        Args:
+            sequence (:obj:`str`):
+                A sequence to tokenize
+
+        Returns:
+            A :obj:`List` of :class:`~tokenizers.Token`: The generated tokens
+        """
+        pass
+
+class WordLevel(Model):
+    """
+    An implementation of the WordLevel algorithm
+
+    Most simple tokenizer model based on mapping tokens to their corresponding id.
+
+    Args:
+        vocab (:obj:`str`, `optional`):
+            A dictionary of string keys and their ids :obj:`{"am": 0,...}`
+
+        unk_token (:obj:`str`, `optional`):
+            The unknown token to be used by the model.
+    """
+    def __init__(self, vocab=None, unk_token=None):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def from_file(vocab, unk_token=None):
+        """
+        Instantiate a WordLevel model from the given file
+
+        This method is roughly equivalent to doing::
+
+            vocab = WordLevel.read_file(vocab_filename)
+            wordlevel = WordLevel(vocab)
+
+        If you don't need to keep the :obj:`vocab` values lying around, this method is
+        more optimized than manually calling :meth:`~tokenizers.models.WordLevel.read_file` to
+        initialize a :class:`~tokenizers.models.WordLevel`
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.json` file
+
+        Returns:
+            :class:`~tokenizers.models.WordLevel`: An instance of WordLevel loaded from file
+        """
+        pass
+
+    def get_trainer(self):
+        """
+        Get the associated :class:`~tokenizers.trainers.Trainer`
+
+        Retrieve the :class:`~tokenizers.trainers.Trainer` associated to this
+        :class:`~tokenizers.models.Model`.
+
+        Returns:
+            :class:`~tokenizers.trainers.Trainer`: The Trainer used to train this model
+        """
+        pass
+
+    def id_to_token(self, id):
+        """
+        Get the token associated to an ID
+
+        Args:
+            id (:obj:`int`):
+                An ID to convert to a token
+
+        Returns:
+            :obj:`str`: The token associated to the ID
+        """
+        pass
+
+    @staticmethod
+    def read_file(vocab):
+        """
+        Read a :obj:`vocab.json`
+
+        This method provides a way to read and parse the content of a vocabulary file,
+        returning the relevant data structures. If you want to instantiate some WordLevel models
+        from memory, this method gives you the expected input from the standard files.
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.json` file
+
+        Returns:
+            :obj:`Dict[str, int]`: The vocabulary as a :obj:`dict`
+        """
+        pass
+
+    def save(self, folder, prefix):
+        """
+        Save the current model
+
+        Save the current model in the given folder, using the given prefix for the various
+        files that will get created.
+        Any file with the same name that already exists in this folder will be overwritten.
+
+        Args:
+            folder (:obj:`str`):
+                The path to the target folder in which to save the various files
+
+            prefix (:obj:`str`, `optional`):
+                An optional prefix, used to prefix each file name
+
+        Returns:
+            :obj:`List[str]`: The list of saved files
+        """
+        pass
+
+    def token_to_id(self, tokens):
+        """
+        Get the ID associated to a token
+
+        Args:
+            token (:obj:`str`):
+                A token to convert to an ID
+
+        Returns:
+            :obj:`int`: The ID associated to the token
+        """
+        pass
+
+    def tokenize(self, sequence):
+        """
+        Tokenize a sequence
+
+        Args:
+            sequence (:obj:`str`):
+                A sequence to tokenize
+
+        Returns:
+            A :obj:`List` of :class:`~tokenizers.Token`: The generated tokens
+        """
+        pass
+
+    @property
+    def unk_token(self):
+        """ """
+        pass
+
+    @unk_token.setter
+    def unk_token(self, value):
+        """ """
+        pass
+
+class WordPiece(Model):
+    """
+    An implementation of the WordPiece algorithm
+
+    Args:
+        vocab (:obj:`Dict[str, int]`, `optional`):
+            A dictionary of string keys and their ids :obj:`{"am": 0,...}`
+
+        unk_token (:obj:`str`, `optional`):
+            The unknown token to be used by the model.
+
+        max_input_chars_per_word (:obj:`int`, `optional`):
+            The maximum number of characters to authorize in a single word.
+    """
+    def __init__(self, vocab=None, unk_token="[UNK]", max_input_chars_per_word=100, continuing_subword_prefix="##"):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def continuing_subword_prefix(self):
+        """ """
+        pass
+
+    @continuing_subword_prefix.setter
+    def continuing_subword_prefix(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def from_file(vocab, **kwargs):
+        """
+        Instantiate a WordPiece model from the given file
+
+        This method is roughly equivalent to doing::
+
+            vocab = WordPiece.read_file(vocab_filename)
+            wordpiece = WordPiece(vocab)
+
+        If you don't need to keep the :obj:`vocab` values lying around, this method is
+        more optimized than manually calling :meth:`~tokenizers.models.WordPiece.read_file` to
+        initialize a :class:`~tokenizers.models.WordPiece`
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.txt` file
+
+        Returns:
+            :class:`~tokenizers.models.WordPiece`: An instance of WordPiece loaded from file
+        """
+        pass
+
+    def get_trainer(self):
+        """
+        Get the associated :class:`~tokenizers.trainers.Trainer`
+
+        Retrieve the :class:`~tokenizers.trainers.Trainer` associated to this
+        :class:`~tokenizers.models.Model`.
+
+        Returns:
+            :class:`~tokenizers.trainers.Trainer`: The Trainer used to train this model
+        """
+        pass
+
+    def id_to_token(self, id):
+        """
+        Get the token associated to an ID
+
+        Args:
+            id (:obj:`int`):
+                An ID to convert to a token
+
+        Returns:
+            :obj:`str`: The token associated to the ID
+        """
+        pass
+
+    @property
+    def max_input_chars_per_word(self):
+        """ """
+        pass
+
+    @max_input_chars_per_word.setter
+    def max_input_chars_per_word(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def read_file(vocab):
+        """
+        Read a :obj:`vocab.txt` file
+
+        This method provides a way to read and parse the content of a standard `vocab.txt`
+        file as used by the WordPiece Model, returning the relevant data structures. If you
+        want to instantiate some WordPiece models from memory, this method gives you the
+        expected input from the standard files.
+
+        Args:
+            vocab (:obj:`str`):
+                The path to a :obj:`vocab.txt` file
+
+        Returns:
+            :obj:`Dict[str, int]`: The vocabulary as a :obj:`dict`
+        """
+        pass
+
+    def save(self, folder, prefix):
+        """
+        Save the current model
+
+        Save the current model in the given folder, using the given prefix for the various
+        files that will get created.
+        Any file with the same name that already exists in this folder will be overwritten.
+
+        Args:
+            folder (:obj:`str`):
+                The path to the target folder in which to save the various files
+
+            prefix (:obj:`str`, `optional`):
+                An optional prefix, used to prefix each file name
+
+        Returns:
+            :obj:`List[str]`: The list of saved files
+        """
+        pass
+
+    def token_to_id(self, tokens):
+        """
+        Get the ID associated to a token
+
+        Args:
+            token (:obj:`str`):
+                A token to convert to an ID
+
+        Returns:
+            :obj:`int`: The ID associated to the token
+        """
+        pass
+
+    def tokenize(self, sequence):
+        """
+        Tokenize a sequence
+
+        Args:
+            sequence (:obj:`str`):
+                A sequence to tokenize
+
+        Returns:
+            A :obj:`List` of :class:`~tokenizers.Token`: The generated tokens
+        """
+        pass
+
+    @property
+    def unk_token(self):
+        """ """
+        pass
+
+    @unk_token.setter
+    def unk_token(self, value):
+        """ """
+        pass
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8dfa99ce2f7da33821f60a9ea9bd26d0bd1884a2
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/models/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..86d233bd216821d77f5ccf88f874b6f530cedbf5
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.py
@@ -0,0 +1,29 @@
+from .. import normalizers
+
+
+Normalizer = normalizers.Normalizer
+BertNormalizer = normalizers.BertNormalizer
+NFD = normalizers.NFD
+NFKD = normalizers.NFKD
+NFC = normalizers.NFC
+NFKC = normalizers.NFKC
+Sequence = normalizers.Sequence
+Lowercase = normalizers.Lowercase
+Prepend = normalizers.Prepend
+Strip = normalizers.Strip
+StripAccents = normalizers.StripAccents
+Nmt = normalizers.Nmt
+Precompiled = normalizers.Precompiled
+Replace = normalizers.Replace
+ByteLevel = normalizers.ByteLevel
+
+NORMALIZERS = {"nfc": NFC, "nfd": NFD, "nfkc": NFKC, "nfkd": NFKD}
+
+
+def unicode_normalizer_from_str(normalizer: str) -> Normalizer:
+    if normalizer not in NORMALIZERS:
+        raise ValueError(
+            "{} is not a known unicode normalizer. Available are {}".format(normalizer, NORMALIZERS.keys())
+        )
+
+    return NORMALIZERS[normalizer]()
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..8d920e0ed73ae051f2135aa250d2426562b73a43
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__init__.pyi
@@ -0,0 +1,946 @@
+# Generated content DO NOT EDIT
+class Normalizer:
+    """
+    Base class for all normalizers
+
+    This class is not supposed to be instantiated directly. Instead, any implementation of a
+    Normalizer will return an instance of this class when instantiated.
+    """
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class BertNormalizer(Normalizer):
+    """
+    BertNormalizer
+
+    Takes care of normalizing raw text before giving it to a Bert model.
+    This includes cleaning the text, handling accents, chinese chars and lowercasing
+
+    Args:
+        clean_text (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to clean the text, by removing any control characters
+            and replacing all whitespaces by the classic one.
+
+        handle_chinese_chars (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to handle chinese chars by putting spaces around them.
+
+        strip_accents (:obj:`bool`, `optional`):
+            Whether to strip all accents. If this option is not specified (ie == None),
+            then it will be determined by the value for `lowercase` (as in the original Bert).
+
+        lowercase (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to lowercase.
+    """
+    def __init__(self, clean_text=True, handle_chinese_chars=True, strip_accents=None, lowercase=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def clean_text(self):
+        """ """
+        pass
+
+    @clean_text.setter
+    def clean_text(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    @property
+    def handle_chinese_chars(self):
+        """ """
+        pass
+
+    @handle_chinese_chars.setter
+    def handle_chinese_chars(self, value):
+        """ """
+        pass
+
+    @property
+    def lowercase(self):
+        """ """
+        pass
+
+    @lowercase.setter
+    def lowercase(self, value):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+    @property
+    def strip_accents(self):
+        """ """
+        pass
+
+    @strip_accents.setter
+    def strip_accents(self, value):
+        """ """
+        pass
+
+class ByteLevel(Normalizer):
+    """
+    Bytelevel Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class Lowercase(Normalizer):
+    """
+    Lowercase Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class NFC(Normalizer):
+    """
+    NFC Unicode Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class NFD(Normalizer):
+    """
+    NFD Unicode Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class NFKC(Normalizer):
+    """
+    NFKC Unicode Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class NFKD(Normalizer):
+    """
+    NFKD Unicode Normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class Nmt(Normalizer):
+    """
+    Nmt normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class Precompiled(Normalizer):
+    """
+    Precompiled normalizer
+    Don't use manually it is used for compatibility for SentencePiece.
+    """
+    def __init__(self, precompiled_charsmap):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class Prepend(Normalizer):
+    """
+    Prepend normalizer
+    """
+    def __init__(self, prepend):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+    @property
+    def prepend(self):
+        """ """
+        pass
+
+    @prepend.setter
+    def prepend(self, value):
+        """ """
+        pass
+
+class Replace(Normalizer):
+    """
+    Replace normalizer
+    """
+    def __init__(self, pattern, content):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def content(self):
+        """ """
+        pass
+
+    @content.setter
+    def content(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+    @property
+    def pattern(self):
+        """ """
+        pass
+
+    @pattern.setter
+    def pattern(self, value):
+        """ """
+        pass
+
+class Sequence(Normalizer):
+    """
+    Allows concatenating multiple other Normalizer as a Sequence.
+    All the normalizers run in sequence in the given order
+
+    Args:
+        normalizers (:obj:`List[Normalizer]`):
+            A list of Normalizer to be run as a sequence
+    """
+    def __init__(self, normalizers):
+        pass
+
+    def __getitem__(self, key):
+        """
+        Return self[key].
+        """
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setitem__(self, key, value):
+        """
+        Set self[key] to value.
+        """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+class Strip(Normalizer):
+    """
+    Strip normalizer
+    """
+    def __init__(self, left=True, right=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    @property
+    def left(self):
+        """ """
+        pass
+
+    @left.setter
+    def left(self, value):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+    @property
+    def right(self):
+        """ """
+        pass
+
+    @right.setter
+    def right(self, value):
+        """ """
+        pass
+
+class StripAccents(Normalizer):
+    """
+    StripAccents normalizer
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(normalizer):
+        """ """
+        pass
+
+    def normalize(self, normalized):
+        """
+        Normalize a :class:`~tokenizers.NormalizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.NormalizedString` to
+        keep track of the alignment information. If you just want to see the result
+        of the normalization on a raw string, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize_str`
+
+        Args:
+            normalized (:class:`~tokenizers.NormalizedString`):
+                The normalized string on which to apply this
+                :class:`~tokenizers.normalizers.Normalizer`
+        """
+        pass
+
+    def normalize_str(self, sequence):
+        """
+        Normalize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.normalizers.Normalizer` but it does not keep track of the alignment
+        information. If you need to get/convert offsets, you can use
+        :meth:`~tokenizers.normalizers.Normalizer.normalize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to normalize
+
+        Returns:
+            :obj:`str`: A string after normalization
+        """
+        pass
+
+from typing import Dict
+
+NORMALIZERS: Dict[str, Normalizer]
+
+def unicode_normalizer_from_str(normalizer: str) -> Normalizer: ...
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..df72929d984dc41fb791217d447b954cda391b3c
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/normalizers/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..db8ddc20805b1c525be405134f8fa722ace89667
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.py
@@ -0,0 +1,16 @@
+# Generated content DO NOT EDIT
+from .. import pre_tokenizers
+
+PreTokenizer = pre_tokenizers.PreTokenizer
+BertPreTokenizer = pre_tokenizers.BertPreTokenizer
+ByteLevel = pre_tokenizers.ByteLevel
+CharDelimiterSplit = pre_tokenizers.CharDelimiterSplit
+Digits = pre_tokenizers.Digits
+FixedLength = pre_tokenizers.FixedLength
+Metaspace = pre_tokenizers.Metaspace
+Punctuation = pre_tokenizers.Punctuation
+Sequence = pre_tokenizers.Sequence
+Split = pre_tokenizers.Split
+UnicodeScripts = pre_tokenizers.UnicodeScripts
+Whitespace = pre_tokenizers.Whitespace
+WhitespaceSplit = pre_tokenizers.WhitespaceSplit
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..1e58d5d040816761987935facee50666221a94bd
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__init__.pyi
@@ -0,0 +1,1015 @@
+# Generated content DO NOT EDIT
+class PreTokenizer:
+    """
+    Base class for all pre-tokenizers
+
+    This class is not supposed to be instantiated directly. Instead, any implementation of a
+    PreTokenizer will return an instance of this class when instantiated.
+    """
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class BertPreTokenizer(PreTokenizer):
+    """
+    BertPreTokenizer
+
+    This pre-tokenizer splits tokens on spaces, and also on punctuation.
+    Each occurrence of a punctuation character will be treated separately.
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class ByteLevel(PreTokenizer):
+    """
+    ByteLevel PreTokenizer
+
+    This pre-tokenizer takes care of replacing all bytes of the given string
+    with a corresponding representation, as well as splitting into words.
+
+    Args:
+        add_prefix_space (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to add a space to the first word if there isn't already one. This
+            lets us treat `hello` exactly like `say hello`.
+        use_regex (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Set this to :obj:`False` to prevent this `pre_tokenizer` from using
+            the GPT2 specific regexp for spliting on whitespace.
+    """
+    def __init__(self, add_prefix_space=True, trim_offsets=True, use_regex=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def add_prefix_space(self):
+        """ """
+        pass
+
+    @add_prefix_space.setter
+    def add_prefix_space(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def alphabet():
+        """
+        Returns the alphabet used by this PreTokenizer.
+
+        Since the ByteLevel works as its name suggests, at the byte level, it
+        encodes each byte value to a unique visible character. This means that there is a
+        total of 256 different characters composing this alphabet.
+
+        Returns:
+            :obj:`List[str]`: A list of characters that compose the alphabet
+        """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+    @property
+    def trim_offsets(self):
+        """ """
+        pass
+
+    @trim_offsets.setter
+    def trim_offsets(self, value):
+        """ """
+        pass
+
+    @property
+    def use_regex(self):
+        """ """
+        pass
+
+    @use_regex.setter
+    def use_regex(self, value):
+        """ """
+        pass
+
+class CharDelimiterSplit(PreTokenizer):
+    """
+    This pre-tokenizer simply splits on the provided char. Works like `.split(delimiter)`
+
+    Args:
+        delimiter: str:
+            The delimiter char that will be used to split input
+    """
+    def __init__(self, delimiter):
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    @property
+    def delimiter(self):
+        """ """
+        pass
+
+    @delimiter.setter
+    def delimiter(self, value):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class Digits(PreTokenizer):
+    """
+    This pre-tokenizer simply splits using the digits in separate tokens
+
+    Args:
+        individual_digits (:obj:`bool`, `optional`, defaults to :obj:`False`):
+            If set to True, digits will each be separated as follows::
+
+                "Call 123 please" -> "Call ", "1", "2", "3", " please"
+
+            If set to False, digits will grouped as follows::
+
+                "Call 123 please" -> "Call ", "123", " please"
+    """
+    def __init__(self, individual_digits=False):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    @property
+    def individual_digits(self):
+        """ """
+        pass
+
+    @individual_digits.setter
+    def individual_digits(self, value):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class FixedLength(PreTokenizer):
+    """
+    This pre-tokenizer splits the text into fixed length chunks as used
+    [here](https://www.biorxiv.org/content/10.1101/2023.01.11.523679v1.full)
+
+    Args:
+        length (:obj:`int`, `optional`, defaults to :obj:`5`):
+            The length of the chunks to split the text into.
+
+            Strings are split on the character level rather than the byte level to avoid
+            splitting unicode characters consisting of multiple bytes.
+    """
+    def __init__(self, length=5):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    @property
+    def length(self):
+        """ """
+        pass
+
+    @length.setter
+    def length(self, value):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class Metaspace(PreTokenizer):
+    """
+    Metaspace pre-tokenizer
+
+    This pre-tokenizer replaces any whitespace by the provided replacement character.
+    It then tries to split on these spaces.
+
+    Args:
+        replacement (:obj:`str`, `optional`, defaults to :obj:`▁`):
+            The replacement character. Must be exactly one character. By default we
+            use the `▁` (U+2581) meta symbol (Same as in SentencePiece).
+
+        prepend_scheme (:obj:`str`, `optional`, defaults to :obj:`"always"`):
+            Whether to add a space to the first word if there isn't already one. This
+            lets us treat `hello` exactly like `say hello`.
+            Choices: "always", "never", "first". First means the space is only added on the first
+            token (relevant when special tokens are used or other pre_tokenizer are used).
+
+    """
+    def __init__(self, replacement="_", prepend_scheme="always", split=True):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+    @property
+    def prepend_scheme(self):
+        """ """
+        pass
+
+    @prepend_scheme.setter
+    def prepend_scheme(self, value):
+        """ """
+        pass
+
+    @property
+    def replacement(self):
+        """ """
+        pass
+
+    @replacement.setter
+    def replacement(self, value):
+        """ """
+        pass
+
+    @property
+    def split(self):
+        """ """
+        pass
+
+    @split.setter
+    def split(self, value):
+        """ """
+        pass
+
+class Punctuation(PreTokenizer):
+    """
+    This pre-tokenizer simply splits on punctuation as individual characters.
+
+    Args:
+        behavior (:class:`~tokenizers.SplitDelimiterBehavior`):
+            The behavior to use when splitting.
+            Choices: "removed", "isolated" (default), "merged_with_previous", "merged_with_next",
+            "contiguous"
+    """
+    def __init__(self, behavior="isolated"):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def behavior(self):
+        """ """
+        pass
+
+    @behavior.setter
+    def behavior(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class Sequence(PreTokenizer):
+    """
+    This pre-tokenizer composes other pre_tokenizers and applies them in sequence
+    """
+    def __init__(self, pretokenizers):
+        pass
+
+    def __getitem__(self, key):
+        """
+        Return self[key].
+        """
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setitem__(self, key, value):
+        """
+        Set self[key] to value.
+        """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class Split(PreTokenizer):
+    """
+    Split PreTokenizer
+
+    This versatile pre-tokenizer splits using the provided pattern and
+    according to the provided behavior. The pattern can be inverted by
+    making use of the invert flag.
+
+    Args:
+        pattern (:obj:`str` or :class:`~tokenizers.Regex`):
+            A pattern used to split the string. Usually a string or a regex built with `tokenizers.Regex`.
+            If you want to use a regex pattern, it has to be wrapped around a `tokenizers.Regex`,
+            otherwise we consider is as a string pattern. For example `pattern="|"`
+            means you want to split on `|` (imagine a csv file for example), while
+            `pattern=tokenizers.Regex("1|2")` means you split on either '1' or '2'.
+        behavior (:class:`~tokenizers.SplitDelimiterBehavior`):
+            The behavior to use when splitting.
+            Choices: "removed", "isolated", "merged_with_previous", "merged_with_next",
+            "contiguous"
+
+        invert (:obj:`bool`, `optional`, defaults to :obj:`False`):
+            Whether to invert the pattern.
+    """
+    def __init__(self, pattern, behavior, invert=False):
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def behavior(self):
+        """ """
+        pass
+
+    @behavior.setter
+    def behavior(self, value):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    @property
+    def invert(self):
+        """ """
+        pass
+
+    @invert.setter
+    def invert(self, value):
+        """ """
+        pass
+
+    @property
+    def pattern(self):
+        """ """
+        pass
+
+    @pattern.setter
+    def pattern(self, value):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class UnicodeScripts(PreTokenizer):
+    """
+    This pre-tokenizer splits on characters that belong to different language family
+    It roughly follows https://github.com/google/sentencepiece/blob/master/data/Scripts.txt
+    Actually Hiragana and Katakana are fused with Han, and 0x30FC is Han too.
+    This mimicks SentencePiece Unigram implementation.
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class Whitespace(PreTokenizer):
+    """
+    This pre-tokenizer splits on word boundaries according to the `\w+|[^\w\s]+`
+    regex pattern. It splits on word characters or characters that aren't words or
+    whitespaces (punctuation such as hyphens, apostrophes, commas, etc.).
+
+    Example:
+        Use the `Whitespace` function as shown below::
+
+            ```python
+            from tokenizers.pre_tokenizers import Whitespace
+
+            pre_tokenizer = Whitespace()
+            text = "Hello, world! Let's try the Whitespace pre-tokenizer."
+            pre_tokenizer.pre_tokenize_str(text)
+            [('Hello', (0, 5)),
+             (',', (5, 6)),
+             ('world', (7, 12)),
+             ('!', (12, 13)),
+             ('Let', (14, 17)),
+             ("'", (17, 18)),
+             ('s', (18, 19)),
+             ('try', (20, 23)),
+             ('the', (24, 27)),
+             ('Whitespace', (28, 38)),
+             ('pre', (39, 42)),
+             ('-', (42, 43)),
+             ('tokenizer', (43, 52)),
+             ('.', (52, 53))]
+            ```
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
+
+class WhitespaceSplit(PreTokenizer):
+    """
+    This pre-tokenizer simply splits on the whitespace. Works like `.split()`
+    """
+    def __init__(self):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @staticmethod
+    def custom(pretok):
+        """ """
+        pass
+
+    def pre_tokenize(self, pretok):
+        """
+        Pre-tokenize a :class:`~tokenizers.PyPreTokenizedString` in-place
+
+        This method allows to modify a :class:`~tokenizers.PreTokenizedString` to
+        keep track of the pre-tokenization, and leverage the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you just want to see the result of
+        the pre-tokenization of a raw string, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize_str`
+
+        Args:
+            pretok (:class:`~tokenizers.PreTokenizedString):
+                The pre-tokenized string on which to apply this
+                :class:`~tokenizers.pre_tokenizers.PreTokenizer`
+        """
+        pass
+
+    def pre_tokenize_str(self, sequence):
+        """
+        Pre tokenize the given string
+
+        This method provides a way to visualize the effect of a
+        :class:`~tokenizers.pre_tokenizers.PreTokenizer` but it does not keep track of the
+        alignment, nor does it provide all the capabilities of the
+        :class:`~tokenizers.PreTokenizedString`. If you need some of these, you can use
+        :meth:`~tokenizers.pre_tokenizers.PreTokenizer.pre_tokenize`
+
+        Args:
+            sequence (:obj:`str`):
+                A string to pre-tokeize
+
+        Returns:
+            :obj:`List[Tuple[str, Offsets]]`:
+                A list of tuple with the pre-tokenized parts and their offsets
+        """
+        pass
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..89cf526404afcb911d595510b03319c6ea2589df
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/pre_tokenizers/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..06d124037b6d932615fa0d31b02f8ac82ac0b5fc
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.py
@@ -0,0 +1,9 @@
+# Generated content DO NOT EDIT
+from .. import processors
+
+PostProcessor = processors.PostProcessor
+BertProcessing = processors.BertProcessing
+ByteLevel = processors.ByteLevel
+RobertaProcessing = processors.RobertaProcessing
+Sequence = processors.Sequence
+TemplateProcessing = processors.TemplateProcessing
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..0d49520c63e56c90b45c4a24604938daebbaeb07
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__init__.pyi
@@ -0,0 +1,519 @@
+# Generated content DO NOT EDIT
+class PostProcessor:
+    """
+    Base class for all post-processors
+
+    This class is not supposed to be instantiated directly. Instead, any implementation of
+    a PostProcessor will return an instance of this class when instantiated.
+    """
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+class BertProcessing(PostProcessor):
+    """
+    This post-processor takes care of adding the special tokens needed by
+    a Bert model:
+
+        - a SEP token
+        - a CLS token
+
+    Args:
+        sep (:obj:`Tuple[str, int]`):
+            A tuple with the string representation of the SEP token, and its id
+
+        cls (:obj:`Tuple[str, int]`):
+            A tuple with the string representation of the CLS token, and its id
+    """
+    def __init__(self, sep, cls):
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def cls(self):
+        """ """
+        pass
+
+    @cls.setter
+    def cls(self, value):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+    @property
+    def sep(self):
+        """ """
+        pass
+
+    @sep.setter
+    def sep(self, value):
+        """ """
+        pass
+
+class ByteLevel(PostProcessor):
+    """
+    This post-processor takes care of trimming the offsets.
+
+    By default, the ByteLevel BPE might include whitespaces in the produced tokens. If you don't
+    want the offsets to include these whitespaces, then this PostProcessor must be used.
+
+    Args:
+        trim_offsets (:obj:`bool`):
+            Whether to trim the whitespaces from the produced offsets.
+
+        add_prefix_space (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            If :obj:`True`, keeps the first token's offset as is. If :obj:`False`, increments
+            the start of the first token's offset by 1. Only has an effect if :obj:`trim_offsets`
+            is set to :obj:`True`.
+    """
+    def __init__(self, add_prefix_space=None, trim_offsets=None, use_regex=None):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def add_prefix_space(self):
+        """ """
+        pass
+
+    @add_prefix_space.setter
+    def add_prefix_space(self, value):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+    @property
+    def trim_offsets(self):
+        """ """
+        pass
+
+    @trim_offsets.setter
+    def trim_offsets(self, value):
+        """ """
+        pass
+
+    @property
+    def use_regex(self):
+        """ """
+        pass
+
+    @use_regex.setter
+    def use_regex(self, value):
+        """ """
+        pass
+
+class RobertaProcessing(PostProcessor):
+    """
+    This post-processor takes care of adding the special tokens needed by
+    a Roberta model:
+
+        - a SEP token
+        - a CLS token
+
+    It also takes care of trimming the offsets.
+    By default, the ByteLevel BPE might include whitespaces in the produced tokens. If you don't
+    want the offsets to include these whitespaces, then this PostProcessor should be initialized
+    with :obj:`trim_offsets=True`
+
+    Args:
+        sep (:obj:`Tuple[str, int]`):
+            A tuple with the string representation of the SEP token, and its id
+
+        cls (:obj:`Tuple[str, int]`):
+            A tuple with the string representation of the CLS token, and its id
+
+        trim_offsets (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether to trim the whitespaces from the produced offsets.
+
+        add_prefix_space (:obj:`bool`, `optional`, defaults to :obj:`True`):
+            Whether the add_prefix_space option was enabled during pre-tokenization. This
+            is relevant because it defines the way the offsets are trimmed out.
+    """
+    def __init__(self, sep, cls, trim_offsets=True, add_prefix_space=True):
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    @property
+    def add_prefix_space(self):
+        """ """
+        pass
+
+    @add_prefix_space.setter
+    def add_prefix_space(self, value):
+        """ """
+        pass
+
+    @property
+    def cls(self):
+        """ """
+        pass
+
+    @cls.setter
+    def cls(self, value):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+    @property
+    def sep(self):
+        """ """
+        pass
+
+    @sep.setter
+    def sep(self, value):
+        """ """
+        pass
+
+    @property
+    def trim_offsets(self):
+        """ """
+        pass
+
+    @trim_offsets.setter
+    def trim_offsets(self, value):
+        """ """
+        pass
+
+class Sequence(PostProcessor):
+    """
+    Sequence Processor
+
+    Args:
+        processors (:obj:`List[PostProcessor]`)
+            The processors that need to be chained
+    """
+    def __init__(self, processors):
+        pass
+
+    def __getitem__(self, key):
+        """
+        Return self[key].
+        """
+        pass
+
+    def __getnewargs__(self):
+        """ """
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setitem__(self, key, value):
+        """
+        Set self[key] to value.
+        """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+class TemplateProcessing(PostProcessor):
+    """
+    Provides a way to specify templates in order to add the special tokens to each
+    input sequence as relevant.
+
+    Let's take :obj:`BERT` tokenizer as an example. It uses two special tokens, used to
+    delimitate each sequence. :obj:`[CLS]` is always used at the beginning of the first
+    sequence, and :obj:`[SEP]` is added at the end of both the first, and the pair
+    sequences. The final result looks like this:
+
+        - Single sequence: :obj:`[CLS] Hello there [SEP]`
+        - Pair sequences: :obj:`[CLS] My name is Anthony [SEP] What is my name? [SEP]`
+
+    With the type ids as following::
+
+        [CLS]   ...   [SEP]   ...   [SEP]
+          0      0      0      1      1
+
+    You can achieve such behavior using a TemplateProcessing::
+
+        TemplateProcessing(
+            single="[CLS] $0 [SEP]",
+            pair="[CLS] $A [SEP] $B:1 [SEP]:1",
+            special_tokens=[("[CLS]", 1), ("[SEP]", 0)],
+        )
+
+    In this example, each input sequence is identified using a ``$`` construct. This identifier
+    lets us specify each input sequence, and the type_id to use. When nothing is specified,
+    it uses the default values. Here are the different ways to specify it:
+
+        - Specifying the sequence, with default ``type_id == 0``: ``$A`` or ``$B``
+        - Specifying the `type_id` with default ``sequence == A``: ``$0``, ``$1``, ``$2``, ...
+        - Specifying both: ``$A:0``, ``$B:1``, ...
+
+    The same construct is used for special tokens: ``(:)?``.
+
+    **Warning**: You must ensure that you are giving the correct tokens/ids as these
+    will be added to the Encoding without any further check. If the given ids correspond
+    to something totally different in a `Tokenizer` using this `PostProcessor`, it
+    might lead to unexpected results.
+
+    Args:
+        single (:obj:`Template`):
+            The template used for single sequences
+
+        pair (:obj:`Template`):
+            The template used when both sequences are specified
+
+        special_tokens (:obj:`Tokens`):
+            The list of special tokens used in each sequences
+
+    Types:
+
+        Template (:obj:`str` or :obj:`List`):
+            - If a :obj:`str` is provided, the whitespace is used as delimiter between tokens
+            - If a :obj:`List[str]` is provided, a list of tokens
+
+        Tokens (:obj:`List[Union[Tuple[int, str], Tuple[str, int], dict]]`):
+            - A :obj:`Tuple` with both a token and its associated ID, in any order
+            - A :obj:`dict` with the following keys:
+                - "id": :obj:`str` => The special token id, as specified in the Template
+                - "ids": :obj:`List[int]` => The associated IDs
+                - "tokens": :obj:`List[str]` => The associated tokens
+
+             The given dict expects the provided :obj:`ids` and :obj:`tokens` lists to have
+             the same length.
+    """
+    def __init__(self, single=None, pair=None, special_tokens=None):
+        pass
+
+    def __getstate__(self):
+        """ """
+        pass
+
+    def __setstate__(self, state):
+        """ """
+        pass
+
+    def num_special_tokens_to_add(self, is_pair):
+        """
+        Return the number of special tokens that would be added for single/pair sentences.
+
+        Args:
+            is_pair (:obj:`bool`):
+                Whether the input would be a pair of sequences
+
+        Returns:
+            :obj:`int`: The number of tokens to add
+        """
+        pass
+
+    def process(self, encoding, pair=None, add_special_tokens=True):
+        """
+        Post-process the given encodings, generating the final one
+
+        Args:
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding for the first sequence
+
+            pair (:class:`~tokenizers.Encoding`, `optional`):
+                The encoding for the pair sequence
+
+            add_special_tokens (:obj:`bool`):
+                Whether to add the special tokens
+
+        Return:
+            :class:`~tokenizers.Encoding`: The final encoding
+        """
+        pass
+
+    @property
+    def single(self):
+        """ """
+        pass
+
+    @single.setter
+    def single(self, value):
+        """ """
+        pass
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a54ea5a3ce73918acf923606be6dae2a72d326ca
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/processors/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f941e2ed39c7d69fa14abff7dcf973d93843ea06
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__init__.py
@@ -0,0 +1 @@
+from .visualizer import Annotation, EncodingVisualizer
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..04457e07f77c4b9ce8e0e71365db47bfa1836962
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/__init__.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/visualizer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/visualizer.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..35d446e32669e42c935a2a7c050f465e587df43d
Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/__pycache__/visualizer.cpython-312.pyc differ
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer-styles.css b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer-styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..f54fde45ada66c902c0b41969d0f40d51c9717da
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer-styles.css
@@ -0,0 +1,170 @@
+.tokenized-text {
+    width:100%;
+    padding:2rem;
+    max-height: 400px;
+    overflow-y: auto;
+    box-sizing:border-box;
+    line-height:4rem; /* Lots of space between lines */
+    font-family: "Roboto Light", "Ubuntu Light", "Ubuntu", monospace;
+    box-shadow: 2px 2px 2px rgba(0,0,0,0.2);
+    background-color: rgba(0,0,0,0.01);
+    letter-spacing:2px; /* Give some extra separation between chars */
+}
+.non-token{
+    /* White space and other things the tokenizer ignores*/
+    white-space: pre;
+    letter-spacing:4px;
+    border-top:1px solid #A0A0A0; /* A gentle border on top and bottom makes tabs more ovious*/
+    border-bottom:1px solid #A0A0A0;
+    line-height: 1rem;
+    height: calc(100% - 2px);
+}
+
+.token {
+    white-space: pre;
+    position:relative;
+    color:black;
+    letter-spacing:2px;
+}
+
+.annotation{
+    white-space:nowrap; /* Important - ensures that annotations appears even if the annotated text wraps a line */
+    border-radius:4px;
+    position:relative;
+    width:fit-content;
+}
+.annotation:before {
+    /*The before holds the text and the after holds the background*/
+    z-index:1000; /* Make sure this is above the background */
+    content:attr(data-label); /* The annotations label is on a data attribute */
+    color:white;
+    position:absolute;
+    font-size:1rem;
+    text-align:center;
+    font-weight:bold;
+
+    top:1.75rem;
+    line-height:0;
+    left:0;
+    width:100%;
+    padding:0.5rem 0;
+    /* These make it so an annotation doesn't stretch beyond the annotated text if the label is longer*/
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow:ellipsis;
+}
+
+.annotation:after {
+    content:attr(data-label); /* The content defines the width of the annotation*/
+    position:absolute;
+    font-size:0.75rem;
+    text-align:center;
+    font-weight:bold;
+    text-overflow:ellipsis;
+    top:1.75rem;
+    line-height:0;
+    overflow: hidden;
+    white-space: nowrap;
+
+    left:0;
+    width:100%; /* 100% of the parent, which is the annotation whose width is the tokens inside it*/
+
+    padding:0.5rem 0;
+    /* Nast hack below:
+    We set the annotations color in code because we don't know the colors at css time.
+    But you can't pass a color as a data attribute to get it into the pseudo element (this thing)
+    So to get around that, annotations have the color set on them with a style attribute and then we
+    can get the color with currentColor.
+    Annotations wrap tokens and tokens set the color back to black
+     */
+    background-color: currentColor;
+}
+.annotation:hover::after, .annotation:hover::before{
+    /* When the user hovers over an annotation expand the label to display in full
+     */
+    min-width: fit-content;
+}
+
+.annotation:hover{
+    /* Emphasize the annotation start end with a border on hover*/
+    border-color: currentColor;
+    border: 2px solid;
+}
+.special-token:not(:empty){
+    /*
+    A none empty special token is like UNK (as opposed to CLS which has no representation in the text )
+     */
+    position:relative;
+}
+.special-token:empty::before{
+    /* Special tokens that don't have text are displayed as pseudo elements so we dont select them with the mouse*/
+    content:attr(data-stok);
+    background:#202020;
+    font-size:0.75rem;
+    color:white;
+    margin: 0 0.25rem;
+    padding: 0.25rem;
+    border-radius:4px
+}
+
+.special-token:not(:empty):before {
+    /* Special tokens that have text (UNK) are displayed above the actual text*/
+    content:attr(data-stok);
+    position:absolute;
+    bottom:1.75rem;
+    min-width:100%;
+    width:100%;
+    height:1rem;
+    line-height:1rem;
+    font-size:1rem;
+    text-align:center;
+    color:white;
+    font-weight:bold;
+    background:#202020;
+    border-radius:10%;
+}
+/*
+We want to alternate the color of tokens, but we can't use nth child because tokens might be broken up by annotations
+instead we apply even and odd class at generation time and color them that way
+ */
+.even-token{
+    background:#DCDCDC	;
+    border: 1px solid #DCDCDC;
+}
+.odd-token{
+    background:#A0A0A0;
+    border: 1px solid #A0A0A0;
+}
+.even-token.multi-token,.odd-token.multi-token{
+    background:  repeating-linear-gradient(
+    45deg,
+    transparent,
+    transparent 1px,
+    #ccc 1px,
+    #ccc 1px
+    ),
+    /* on "bottom" */
+    linear-gradient(
+    to bottom,
+    #FFB6C1,
+    #999
+    );
+}
+
+.multi-token:hover::after {
+    content:"This char has more than 1 token"; /* The content defines the width of the annotation*/
+    color:white;
+    background-color: black;
+    position:absolute;
+    font-size:0.75rem;
+    text-align:center;
+    font-weight:bold;
+    text-overflow:ellipsis;
+    top:1.75rem;
+    line-height:0;
+    overflow: hidden;
+    white-space: nowrap;
+    left:0;
+    width:fit-content; /* 100% of the parent, which is the annotation whose width is the tokens inside it*/
+    padding:0.5rem 0;
+}
diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e85f13e05baea5cec69136eb3c951cf28a84207
--- /dev/null
+++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/tools/visualizer.py
@@ -0,0 +1,407 @@
+import itertools
+import os
+import re
+from string import Template
+from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
+
+from tokenizers import Encoding, Tokenizer
+
+
+dirname = os.path.dirname(__file__)
+css_filename = os.path.join(dirname, "visualizer-styles.css")
+with open(css_filename) as f:
+    css = f.read()
+
+
+class Annotation:
+    start: int
+    end: int
+    label: str
+
+    def __init__(self, start: int, end: int, label: str):
+        self.start = start
+        self.end = end
+        self.label = label
+
+
+AnnotationList = List[Annotation]
+PartialIntList = List[Optional[int]]
+
+
+class CharStateKey(NamedTuple):
+    token_ix: Optional[int]
+    anno_ix: Optional[int]
+
+
+class CharState:
+    char_ix: Optional[int]
+
+    def __init__(self, char_ix):
+        self.char_ix = char_ix
+
+        self.anno_ix: Optional[int] = None
+        self.tokens: List[int] = []
+
+    @property
+    def token_ix(self):
+        return self.tokens[0] if len(self.tokens) > 0 else None
+
+    @property
+    def is_multitoken(self):
+        """
+        BPE tokenizers can output more than one token for a char
+        """
+        return len(self.tokens) > 1
+
+    def partition_key(self) -> CharStateKey:
+        return CharStateKey(
+            token_ix=self.token_ix,
+            anno_ix=self.anno_ix,
+        )
+
+
+class Aligned:
+    pass
+
+
+class EncodingVisualizer:
+    """
+    Build an EncodingVisualizer
+
+    Args:
+
+         tokenizer (:class:`~tokenizers.Tokenizer`):
+            A tokenizer instance
+
+         default_to_notebook (:obj:`bool`):
+            Whether to render html output in a notebook by default
+
+         annotation_converter (:obj:`Callable`, `optional`):
+            An optional (lambda) function that takes an annotation in any format and returns
+            an Annotation object
+    """
+
+    unk_token_regex = re.compile("(.{1}\b)?(unk|oov)(\b.{1})?", flags=re.IGNORECASE)
+
+    def __init__(
+        self,
+        tokenizer: Tokenizer,
+        default_to_notebook: bool = True,
+        annotation_converter: Optional[Callable[[Any], Annotation]] = None,
+    ):
+        if default_to_notebook:
+            try:
+                from IPython.core.display import HTML, display  # type: ignore[attr-defined]
+            except ImportError:
+                raise Exception(
+                    """We couldn't import IPython utils for html display.
+                        Are you running in a notebook?
+                        You can also pass `default_to_notebook=False` to get back raw HTML
+                    """
+                )
+
+        self.tokenizer = tokenizer
+        self.default_to_notebook = default_to_notebook
+        self.annotation_coverter = annotation_converter
+        pass
+
+    def __call__(
+        self,
+        text: str,
+        annotations: Optional[List[Any]] = None,
+        default_to_notebook: Optional[bool] = None,
+    ) -> Optional[str]:
+        """
+        Build a visualization of the given text
+
+        Args:
+            text (:obj:`str`):
+                The text to tokenize
+
+            annotations (:obj:`List[Annotation]`, `optional`):
+                An optional list of annotations of the text. The can either be an annotation class
+                or anything else if you instantiated the visualizer with a converter function
+
+            default_to_notebook (:obj:`bool`, `optional`, defaults to `False`):
+                If True, will render the html in a notebook. Otherwise returns an html string.
+
+        Returns:
+            The HTML string if default_to_notebook is False, otherwise (default) returns None and
+            renders the HTML in the notebook
+
+        """
+        final_default_to_notebook = self.default_to_notebook
+        if default_to_notebook is not None:
+            final_default_to_notebook = default_to_notebook
+        if final_default_to_notebook:
+            try:
+                from IPython.core.display import HTML, display  # type: ignore[attr-defined]
+            except ImportError:
+                raise Exception(
+                    """We couldn't import IPython utils for html display.
+                    Are you running in a notebook?"""
+                )
+        if annotations is None:
+            annotations = []
+        if self.annotation_coverter is not None:
+            annotations = list(map(self.annotation_coverter, annotations))
+        encoding = self.tokenizer.encode(text)
+        html = EncodingVisualizer.__make_html(text, encoding, annotations)
+        if final_default_to_notebook:
+            display(HTML(html))
+        else:
+            return html
+
+    @staticmethod
+    def calculate_label_colors(annotations: AnnotationList) -> Dict[str, str]:
+        """
+        Generates a color palette for all the labels in a given set of annotations
+
+        Args:
+          annotations (:obj:`Annotation`):
+            A list of annotations
+
+        Returns:
+            :obj:`dict`: A dictionary mapping labels to colors in HSL format
+        """
+        if len(annotations) == 0:
+            return {}
+        labels = set(map(lambda x: x.label, annotations))
+        num_labels = len(labels)
+        h_step = int(255 / num_labels)
+        if h_step < 20:
+            h_step = 20
+        s = 32
+        l = 64  # noqa: E741
+        h = 10
+        colors = {}
+
+        for label in sorted(labels):  # sort so we always get the same colors for a given set of labels
+            colors[label] = f"hsl({h},{s}%,{l}%)"
+            h += h_step
+        return colors
+
+    @staticmethod
+    def consecutive_chars_to_html(
+        consecutive_chars_list: List[CharState],
+        text: str,
+        encoding: Encoding,
+    ):
+        """
+        Converts a list of "consecutive chars" into a single HTML element.
+        Chars are consecutive if they fall under the same word, token and annotation.
+        The CharState class is a named tuple with a "partition_key" method that makes it easy to
+        compare if two chars are consecutive.
+
+        Args:
+            consecutive_chars_list (:obj:`List[CharState]`):
+                A list of CharStates that have been grouped together
+
+            text (:obj:`str`):
+                The original text being processed
+
+            encoding (:class:`~tokenizers.Encoding`):
+                The encoding returned from the tokenizer
+
+        Returns:
+            :obj:`str`: The HTML span for a set of consecutive chars
+        """
+        first = consecutive_chars_list[0]
+        if first.char_ix is None:
+            # its a special token
+            stoken = encoding.tokens[first.token_ix]
+            # special tokens are represented as empty spans. We use the data attribute and css
+            # magic to display it
+            return f''
+        # We're not in a special token so this group has a start and end.
+        last = consecutive_chars_list[-1]
+        assert first.char_ix is not None
+        assert last.char_ix is not None
+        start = first.char_ix
+        end = last.char_ix + 1
+        span_text = text[start:end]
+        css_classes = []  # What css classes will we apply on the resulting span
+        data_items = {}  # What data attributes will we apply on the result span
+        if first.token_ix is not None:
+            # We can either be in a token or not (e.g. in white space)
+            css_classes.append("token")
+            if first.is_multitoken:
+                css_classes.append("multi-token")
+            if first.token_ix % 2:
+                # We use this to color alternating tokens.
+                # A token might be split by an annotation that ends in the middle of it, so this
+                # lets us visually indicate a consecutive token despite its possible splitting in
+                # the html markup
+                css_classes.append("odd-token")
+            else:
+                # Like above, but a different color so we can see the tokens alternate
+                css_classes.append("even-token")
+            if EncodingVisualizer.unk_token_regex.search(encoding.tokens[first.token_ix]) is not None:
+                # This is a special token that is in the text. probably UNK
+                css_classes.append("special-token")
+                # TODO is this the right name for the data attribute ?
+                data_items["stok"] = encoding.tokens[first.token_ix]
+        else:
+            # In this case we are looking at a group/single char that is not tokenized.
+            # e.g. white space
+            css_classes.append("non-token")
+        css = f'''class="{" ".join(css_classes)}"'''
+        data = ""
+        for key, val in data_items.items():
+            data += f' data-{key}="{val}"'
+        return f"{span_text}"
+
+    @staticmethod
+    def __make_html(text: str, encoding: Encoding, annotations: AnnotationList) -> str:
+        char_states = EncodingVisualizer.__make_char_states(text, encoding, annotations)
+        current_consecutive_chars = [char_states[0]]
+        prev_anno_ix = char_states[0].anno_ix
+        spans = []
+        label_colors_dict = EncodingVisualizer.calculate_label_colors(annotations)
+        cur_anno_ix = char_states[0].anno_ix
+        if cur_anno_ix is not None:
+            # If we started in an  annotation make a span for it
+            anno = annotations[cur_anno_ix]
+            label = anno.label
+            color = label_colors_dict[label]
+            spans.append(f'')
+
+        for cs in char_states[1:]:
+            cur_anno_ix = cs.anno_ix
+            if cur_anno_ix != prev_anno_ix:
+                # If we've transitioned in or out of an annotation
+                spans.append(
+                    # Create a span from the current consecutive characters
+                    EncodingVisualizer.consecutive_chars_to_html(
+                        current_consecutive_chars,
+                        text=text,
+                        encoding=encoding,
+                    )
+                )
+                current_consecutive_chars = [cs]
+
+                if prev_anno_ix is not None:
+                    # if we transitioned out of an annotation close it's span
+                    spans.append("")
+                if cur_anno_ix is not None:
+                    # If we entered a new annotation make a span for it
+                    anno = annotations[cur_anno_ix]
+                    label = anno.label
+                    color = label_colors_dict[label]
+                    spans.append(f'')
+            prev_anno_ix = cur_anno_ix
+
+            if cs.partition_key() == current_consecutive_chars[0].partition_key():
+                # If the current charchter is in the same "group" as the previous one
+                current_consecutive_chars.append(cs)
+            else:
+                # Otherwise we make a span for the previous group
+                spans.append(
+                    EncodingVisualizer.consecutive_chars_to_html(
+                        current_consecutive_chars,
+                        text=text,
+                        encoding=encoding,
+                    )
+                )
+                # An reset the consecutive_char_list to form a new group
+                current_consecutive_chars = [cs]
+        # All that's left is to fill out the final span
+        # TODO I think there is an edge case here where an annotation's span might not close
+        spans.append(
+            EncodingVisualizer.consecutive_chars_to_html(
+                current_consecutive_chars,
+                text=text,
+                encoding=encoding,
+            )
+        )
+        res = HTMLBody(spans)  # Send the list of spans to the body of our html
+        return res
+
+    @staticmethod
+    def __make_anno_map(text: str, annotations: AnnotationList) -> PartialIntList:
+        """
+        Args:
+            text (:obj:`str`):
+                The raw text we want to align to
+
+            annotations (:obj:`AnnotationList`):
+                A (possibly empty) list of annotations
+
+        Returns:
+            A list of  length len(text) whose entry at index i is None if there is no annotation on
+            character i or k, the index of the annotation that covers index i where k is with
+            respect to the list of annotations
+        """
+        annotation_map = [None] * len(text)
+        for anno_ix, a in enumerate(annotations):
+            for i in range(a.start, a.end):
+                annotation_map[i] = anno_ix
+        return annotation_map
+
+    @staticmethod
+    def __make_char_states(text: str, encoding: Encoding, annotations: AnnotationList) -> List[CharState]:
+        """
+        For each character in the original text, we emit a tuple representing it's "state":
+
+            * which token_ix it corresponds to
+            * which word_ix it corresponds to
+            * which annotation_ix it corresponds to
+
+        Args:
+            text (:obj:`str`):
+                The raw text we want to align to
+
+            annotations (:obj:`List[Annotation]`):
+                A (possibly empty) list of annotations
+
+            encoding: (:class:`~tokenizers.Encoding`):
+                The encoding returned from the tokenizer
+
+        Returns:
+            :obj:`List[CharState]`: A list of CharStates, indicating for each char in the text what
+            it's state is
+        """
+        annotation_map = EncodingVisualizer.__make_anno_map(text, annotations)
+        # Todo make this a dataclass or named tuple
+        char_states: List[CharState] = [CharState(char_ix) for char_ix in range(len(text))]
+        for token_ix, token in enumerate(encoding.tokens):
+            offsets = encoding.token_to_chars(token_ix)
+            if offsets is not None:
+                start, end = offsets
+                for i in range(start, end):
+                    char_states[i].tokens.append(token_ix)
+        for char_ix, anno_ix in enumerate(annotation_map):
+            char_states[char_ix].anno_ix = anno_ix
+
+        return char_states
+
+
+def HTMLBody(children: List[str], css_styles=css) -> str:
+    """
+    Generates the full html with css from a list of html spans
+
+    Args:
+        children (:obj:`List[str]`):
+            A list of strings, assumed to be html elements
+
+        css_styles (:obj:`str`, `optional`):
+            Optional alternative implementation of the css
+
+    Returns:
+        :obj:`str`: An HTML string with style markup
+    """
+    children_text = "".join(children)
+    return f"""
+    
+        
+            
+        
+        
+            
+ {children_text} +
+ + + """ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.py b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22f94c50b7cf63f0b38231ab1ecec88141a678fd --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.py @@ -0,0 +1,8 @@ +# Generated content DO NOT EDIT +from .. import trainers + +Trainer = trainers.Trainer +BpeTrainer = trainers.BpeTrainer +UnigramTrainer = trainers.UnigramTrainer +WordLevelTrainer = trainers.WordLevelTrainer +WordPieceTrainer = trainers.WordPieceTrainer diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.pyi b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..d7bf6c5283afb0d9fa046ffa93bb5501fafe06aa --- /dev/null +++ b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__init__.pyi @@ -0,0 +1,462 @@ +# Generated content DO NOT EDIT +class Trainer: + """ + Base class for all trainers + + This class is not supposed to be instantiated directly. Instead, any implementation of a + Trainer will return an instance of this class when instantiated. + """ + def __getstate__(self): + """ """ + pass + + def __setstate__(self, state): + """ """ + pass + +class BpeTrainer(Trainer): + """ + Trainer capable of training a BPE model + + Args: + vocab_size (:obj:`int`, `optional`): + The size of the final vocabulary, including all tokens and alphabet. + + min_frequency (:obj:`int`, `optional`): + The minimum frequency a pair should have in order to be merged. + + show_progress (:obj:`bool`, `optional`): + Whether to show progress bars while training. + + special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`): + A list of special tokens the model should know of. + + limit_alphabet (:obj:`int`, `optional`): + The maximum different characters to keep in the alphabet. + + initial_alphabet (:obj:`List[str]`, `optional`): + A list of characters to include in the initial alphabet, even + if not seen in the training dataset. + If the strings contain more than one character, only the first one + is kept. + + continuing_subword_prefix (:obj:`str`, `optional`): + A prefix to be used for every subword that is not a beginning-of-word. + + end_of_word_suffix (:obj:`str`, `optional`): + A suffix to be used for every subword that is a end-of-word. + + max_token_length (:obj:`int`, `optional`): + Prevents creating tokens longer than the specified size. + This can help with reducing polluting your vocabulary with + highly repetitive tokens like `======` for wikipedia + + """ + def __init__( + self, + vocab_size=30000, + min_frequency=0, + show_progress=True, + special_tokens=[], + limit_alphabet=None, + initial_alphabet=[], + continuing_subword_prefix=None, + end_of_word_suffix=None, + max_token_length=None, + words={}, + ): + pass + + def __getstate__(self): + """ """ + pass + + def __setstate__(self, state): + """ """ + pass + + @property + def continuing_subword_prefix(self): + """ """ + pass + + @continuing_subword_prefix.setter + def continuing_subword_prefix(self, value): + """ """ + pass + + @property + def end_of_word_suffix(self): + """ """ + pass + + @end_of_word_suffix.setter + def end_of_word_suffix(self, value): + """ """ + pass + + @property + def initial_alphabet(self): + """ """ + pass + + @initial_alphabet.setter + def initial_alphabet(self, value): + """ """ + pass + + @property + def limit_alphabet(self): + """ """ + pass + + @limit_alphabet.setter + def limit_alphabet(self, value): + """ """ + pass + + @property + def max_token_length(self): + """ """ + pass + + @max_token_length.setter + def max_token_length(self, value): + """ """ + pass + + @property + def min_frequency(self): + """ """ + pass + + @min_frequency.setter + def min_frequency(self, value): + """ """ + pass + + @property + def show_progress(self): + """ """ + pass + + @show_progress.setter + def show_progress(self, value): + """ """ + pass + + @property + def special_tokens(self): + """ """ + pass + + @special_tokens.setter + def special_tokens(self, value): + """ """ + pass + + @property + def vocab_size(self): + """ """ + pass + + @vocab_size.setter + def vocab_size(self, value): + """ """ + pass + +class UnigramTrainer(Trainer): + """ + Trainer capable of training a Unigram model + + Args: + vocab_size (:obj:`int`): + The size of the final vocabulary, including all tokens and alphabet. + + show_progress (:obj:`bool`): + Whether to show progress bars while training. + + special_tokens (:obj:`List[Union[str, AddedToken]]`): + A list of special tokens the model should know of. + + initial_alphabet (:obj:`List[str]`): + A list of characters to include in the initial alphabet, even + if not seen in the training dataset. + If the strings contain more than one character, only the first one + is kept. + + shrinking_factor (:obj:`float`): + The shrinking factor used at each step of the training to prune the + vocabulary. + + unk_token (:obj:`str`): + The token used for out-of-vocabulary tokens. + + max_piece_length (:obj:`int`): + The maximum length of a given token. + + n_sub_iterations (:obj:`int`): + The number of iterations of the EM algorithm to perform before + pruning the vocabulary. + """ + def __init__( + self, + vocab_size=8000, + show_progress=True, + special_tokens=[], + initial_alphabet=[], + shrinking_factor=0.75, + unk_token=None, + max_piece_length=16, + n_sub_iterations=2, + ): + pass + + def __getstate__(self): + """ """ + pass + + def __setstate__(self, state): + """ """ + pass + + @property + def initial_alphabet(self): + """ """ + pass + + @initial_alphabet.setter + def initial_alphabet(self, value): + """ """ + pass + + @property + def show_progress(self): + """ """ + pass + + @show_progress.setter + def show_progress(self, value): + """ """ + pass + + @property + def special_tokens(self): + """ """ + pass + + @special_tokens.setter + def special_tokens(self, value): + """ """ + pass + + @property + def vocab_size(self): + """ """ + pass + + @vocab_size.setter + def vocab_size(self, value): + """ """ + pass + +class WordLevelTrainer(Trainer): + """ + Trainer capable of training a WorldLevel model + + Args: + vocab_size (:obj:`int`, `optional`): + The size of the final vocabulary, including all tokens and alphabet. + + min_frequency (:obj:`int`, `optional`): + The minimum frequency a pair should have in order to be merged. + + show_progress (:obj:`bool`, `optional`): + Whether to show progress bars while training. + + special_tokens (:obj:`List[Union[str, AddedToken]]`): + A list of special tokens the model should know of. + """ + def __init__(self, vocab_size=30000, min_frequency=0, show_progress=True, special_tokens=[]): + pass + + def __getstate__(self): + """ """ + pass + + def __setstate__(self, state): + """ """ + pass + + @property + def min_frequency(self): + """ """ + pass + + @min_frequency.setter + def min_frequency(self, value): + """ """ + pass + + @property + def show_progress(self): + """ """ + pass + + @show_progress.setter + def show_progress(self, value): + """ """ + pass + + @property + def special_tokens(self): + """ """ + pass + + @special_tokens.setter + def special_tokens(self, value): + """ """ + pass + + @property + def vocab_size(self): + """ """ + pass + + @vocab_size.setter + def vocab_size(self, value): + """ """ + pass + +class WordPieceTrainer(Trainer): + """ + Trainer capable of training a WordPiece model + + Args: + vocab_size (:obj:`int`, `optional`): + The size of the final vocabulary, including all tokens and alphabet. + + min_frequency (:obj:`int`, `optional`): + The minimum frequency a pair should have in order to be merged. + + show_progress (:obj:`bool`, `optional`): + Whether to show progress bars while training. + + special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`): + A list of special tokens the model should know of. + + limit_alphabet (:obj:`int`, `optional`): + The maximum different characters to keep in the alphabet. + + initial_alphabet (:obj:`List[str]`, `optional`): + A list of characters to include in the initial alphabet, even + if not seen in the training dataset. + If the strings contain more than one character, only the first one + is kept. + + continuing_subword_prefix (:obj:`str`, `optional`): + A prefix to be used for every subword that is not a beginning-of-word. + + end_of_word_suffix (:obj:`str`, `optional`): + A suffix to be used for every subword that is a end-of-word. + """ + def __init__( + self, + vocab_size=30000, + min_frequency=0, + show_progress=True, + special_tokens=[], + limit_alphabet=None, + initial_alphabet=[], + continuing_subword_prefix="##", + end_of_word_suffix=None, + ): + pass + + def __getstate__(self): + """ """ + pass + + def __setstate__(self, state): + """ """ + pass + + @property + def continuing_subword_prefix(self): + """ """ + pass + + @continuing_subword_prefix.setter + def continuing_subword_prefix(self, value): + """ """ + pass + + @property + def end_of_word_suffix(self): + """ """ + pass + + @end_of_word_suffix.setter + def end_of_word_suffix(self, value): + """ """ + pass + + @property + def initial_alphabet(self): + """ """ + pass + + @initial_alphabet.setter + def initial_alphabet(self, value): + """ """ + pass + + @property + def limit_alphabet(self): + """ """ + pass + + @limit_alphabet.setter + def limit_alphabet(self, value): + """ """ + pass + + @property + def min_frequency(self): + """ """ + pass + + @min_frequency.setter + def min_frequency(self, value): + """ """ + pass + + @property + def show_progress(self): + """ """ + pass + + @show_progress.setter + def show_progress(self, value): + """ """ + pass + + @property + def special_tokens(self): + """ """ + pass + + @special_tokens.setter + def special_tokens(self, value): + """ """ + pass + + @property + def vocab_size(self): + """ """ + pass + + @vocab_size.setter + def vocab_size(self, value): + """ """ + pass diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dabd91b8832571fedd7f8adc23094bd2c5c42399 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/tokenizers/trainers/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/__init__.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adc71b5465ba17bdf4c7148b6ef965902f2e7d29 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/__init__.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/composer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/composer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c4dfa6abdc524931091cd553af13d9d61138470b Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/composer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/constructor.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/constructor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20c0ad2069cd0eca7f1a166f00c9d23624454287 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/constructor.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/cyaml.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/cyaml.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..811c66ca2078a57452e081691df8e5187dce242f Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/cyaml.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/dumper.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/dumper.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e9586efeab75404e6bac6b38f59a2d27f9416a6 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/dumper.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/emitter.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/emitter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..934762daef7a00a892ab4aa00926c84946f5020e Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/emitter.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/error.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/error.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..526c7c13abc95f6d9dd6785e0bd1ee62b1f77b86 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/error.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/events.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/events.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7e3f27c693f9980e5bb8538f9db3a664c2ccc16 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/events.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/loader.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/loader.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..119eaec7acc92ea7602a59981e060f0b1567ee94 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/loader.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/nodes.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87ff5c60d9e7e291d8d3a74d3f5a280f0dd99684 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/nodes.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/parser.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/parser.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4249df96f30c5c76db126a4ed91a462ee0a845d8 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/parser.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/reader.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/reader.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2bd8f0ce0fe5354df1db86bc7ba73ac92278fac Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/reader.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/representer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/representer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb3f8e502801bdc431ed4fa39abf3780178f5167 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/representer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/resolver.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/resolver.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..485ea5da55f5ae72b33cc016e4ebbfad3034eaf5 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/resolver.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/scanner.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/scanner.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..366aab691ebc14bb456773eec1849a6ae8ec1b69 Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/scanner.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/serializer.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/serializer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd80aa5dc2a5ae5453245c44df4119153cedf3ce Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/serializer.cpython-312.pyc differ diff --git a/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/tokens.cpython-312.pyc b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/tokens.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c7ad4d11a0a0750011719e4dc9a846d4588a89d Binary files /dev/null and b/URSA/.venv_ursa/lib/python3.12/site-packages/yaml/__pycache__/tokens.cpython-312.pyc differ